summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes39
-rw-r--r--.gitignore83
-rw-r--r--.hgignore36
-rw-r--r--CREDITS.md50
-rw-r--r--LICENSE619
-rw-r--r--LICENSE.md596
-rw-r--r--README88
-rw-r--r--README.md270
-rw-r--r--docs/Makefile158
-rw-r--r--docs/access_api.rst20
-rw-r--r--docs/build_docs.py245
-rw-r--r--docs/conf.py238
-rw-r--r--docs/docs.conf7
-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/setup.cfg (renamed from setup.cfg)0
-rw-r--r--docs/write_addons.rst172
-rw-r--r--docs/write_hooks.rst162
-rw-r--r--docs/write_plugins.rst46
-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--lib/Python/Lib/BeautifulSoup.py (renamed from module/lib/BeautifulSoup.py)0
-rw-r--r--lib/Python/Lib/Crypto/Cipher/AES.py115
-rw-r--r--lib/Python/Lib/Crypto/Cipher/ARC2.py130
-rw-r--r--lib/Python/Lib/Crypto/Cipher/ARC4.py120
-rw-r--r--lib/Python/Lib/Crypto/Cipher/Blowfish.py121
-rw-r--r--lib/Python/Lib/Crypto/Cipher/CAST.py123
-rw-r--r--lib/Python/Lib/Crypto/Cipher/DES.py118
-rw-r--r--lib/Python/Lib/Crypto/Cipher/DES3.py133
-rw-r--r--lib/Python/Lib/Crypto/Cipher/PKCS1_OAEP.py255
-rw-r--r--lib/Python/Lib/Crypto/Cipher/PKCS1_v1_5.py226
-rw-r--r--lib/Python/Lib/Crypto/Cipher/XOR.py86
-rw-r--r--lib/Python/Lib/Crypto/Cipher/__init__.py83
-rw-r--r--lib/Python/Lib/Crypto/Cipher/blockalgo.py296
-rw-r--r--lib/Python/Lib/Crypto/Hash/HMAC.py212
-rw-r--r--lib/Python/Lib/Crypto/Hash/MD2.py91
-rw-r--r--lib/Python/Lib/Crypto/Hash/MD4.py91
-rw-r--r--lib/Python/Lib/Crypto/Hash/MD5.py97
-rw-r--r--lib/Python/Lib/Crypto/Hash/RIPEMD.py94
-rw-r--r--lib/Python/Lib/Crypto/Hash/SHA.py98
-rw-r--r--lib/Python/Lib/Crypto/Hash/SHA224.py95
-rw-r--r--lib/Python/Lib/Crypto/Hash/SHA256.py95
-rw-r--r--lib/Python/Lib/Crypto/Hash/SHA384.py96
-rw-r--r--lib/Python/Lib/Crypto/Hash/SHA512.py95
-rw-r--r--lib/Python/Lib/Crypto/Hash/__init__.py56
-rw-r--r--lib/Python/Lib/Crypto/Hash/hashalgo.py116
-rw-r--r--lib/Python/Lib/Crypto/Protocol/AllOrNothing.py319
-rw-r--r--lib/Python/Lib/Crypto/Protocol/Chaffing.py245
-rw-r--r--lib/Python/Lib/Crypto/Protocol/KDF.py123
-rw-r--r--lib/Python/Lib/Crypto/Protocol/__init__.py41
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/DSA.py379
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/ElGamal.py373
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/RSA.py719
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/_DSA.py115
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/_RSA.py81
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/__init__.py41
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/_slowmath.py187
-rw-r--r--lib/Python/Lib/Crypto/PublicKey/pubkey.py240
-rw-r--r--lib/Python/Lib/Crypto/Random/Fortuna/FortunaAccumulator.py171
-rw-r--r--lib/Python/Lib/Crypto/Random/Fortuna/FortunaGenerator.py132
-rw-r--r--lib/Python/Lib/Crypto/Random/Fortuna/SHAd256.py98
-rw-r--r--lib/Python/Lib/Crypto/Random/Fortuna/__init__.py (renamed from module/__init__.py)0
-rw-r--r--lib/Python/Lib/Crypto/Random/OSRNG/__init__.py40
-rw-r--r--lib/Python/Lib/Crypto/Random/OSRNG/fallback.py46
-rw-r--r--lib/Python/Lib/Crypto/Random/OSRNG/nt.py74
-rw-r--r--lib/Python/Lib/Crypto/Random/OSRNG/posix.py86
-rw-r--r--lib/Python/Lib/Crypto/Random/OSRNG/rng_base.py88
-rw-r--r--lib/Python/Lib/Crypto/Random/_UserFriendlyRNG.py230
-rw-r--r--lib/Python/Lib/Crypto/Random/__init__.py43
-rw-r--r--lib/Python/Lib/Crypto/Random/random.py142
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/__init__.py48
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/common.py399
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_AES.py1433
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC2.py124
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC4.py81
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_Blowfish.py113
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_CAST.py57
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES.py339
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES3.py333
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_XOR.py72
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py174
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py372
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/__init__.py52
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/common.py197
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_HMAC.py223
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_MD2.py64
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_MD4.py64
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_MD5.py64
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_RIPEMD.py73
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA.py64
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA224.py65
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA256.py96
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA384.py63
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA512.py60
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Protocol/__init__.py41
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py76
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Protocol/test_KDF.py98
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Protocol/test_chaffing.py74
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Protocol/test_rfc1751.py62
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/PublicKey/__init__.py44
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/PublicKey/test_DSA.py244
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/PublicKey/test_ElGamal.py210
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/PublicKey/test_RSA.py415
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/PublicKey/test_importKey.py345
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/__init__.py44
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py189
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py83
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py55
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/__init__.py49
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py48
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_generic.py48
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_nt.py48
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_posix.py48
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py48
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/__init__.py43
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/test__UserFriendlyRNG.py171
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/test_random.py171
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Random/test_rpoolcompat.py55
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Signature/__init__.py40
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_15.py219
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py446
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Util/__init__.py44
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Util/test_Counter.py165
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Util/test_asn1.py293
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Util/test_number.py295
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/Util/test_winrandom.py48
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/__init__.py92
-rw-r--r--lib/Python/Lib/Crypto/SelfTest/st_common.py62
-rw-r--r--lib/Python/Lib/Crypto/Signature/PKCS1_PSS.py355
-rw-r--r--lib/Python/Lib/Crypto/Signature/PKCS1_v1_5.py236
-rw-r--r--lib/Python/Lib/Crypto/Signature/__init__.py31
-rw-r--r--lib/Python/Lib/Crypto/Util/Counter.py127
-rw-r--r--lib/Python/Lib/Crypto/Util/RFC1751.py364
-rw-r--r--lib/Python/Lib/Crypto/Util/__init__.py37
-rw-r--r--lib/Python/Lib/Crypto/Util/_number_new.py119
-rw-r--r--lib/Python/Lib/Crypto/Util/asn1.py286
-rw-r--r--lib/Python/Lib/Crypto/Util/number.py1456
-rw-r--r--lib/Python/Lib/Crypto/Util/py21compat.py84
-rw-r--r--lib/Python/Lib/Crypto/Util/py3compat.py107
-rw-r--r--lib/Python/Lib/Crypto/Util/randpool.py82
-rw-r--r--lib/Python/Lib/Crypto/Util/winrandom.py28
-rw-r--r--lib/Python/Lib/Crypto/__init__.py51
-rw-r--r--lib/Python/Lib/Crypto/pct_warnings.py60
-rw-r--r--lib/Python/Lib/Getch.py (renamed from module/lib/Getch.py)0
-rw-r--r--lib/Python/Lib/MultipartPostHandler.py (renamed from module/lib/MultipartPostHandler.py)0
-rw-r--r--lib/Python/Lib/OpenSSL/SSL.py1948
-rw-r--r--lib/Python/Lib/OpenSSL/__init__.py12
-rw-r--r--lib/Python/Lib/OpenSSL/_util.py127
-rw-r--r--lib/Python/Lib/OpenSSL/crypto.py2639
-rw-r--r--lib/Python/Lib/OpenSSL/rand.py180
-rw-r--r--lib/Python/Lib/OpenSSL/test/README8
-rw-r--r--lib/Python/Lib/OpenSSL/test/__init__.py6
-rw-r--r--lib/Python/Lib/OpenSSL/test/test_crypto.py3671
-rw-r--r--lib/Python/Lib/OpenSSL/test/test_rand.py223
-rw-r--r--lib/Python/Lib/OpenSSL/test/test_ssl.py3775
-rw-r--r--lib/Python/Lib/OpenSSL/test/test_tsafe.py24
-rw-r--r--lib/Python/Lib/OpenSSL/test/test_util.py17
-rw-r--r--lib/Python/Lib/OpenSSL/test/util.py463
-rw-r--r--lib/Python/Lib/OpenSSL/tsafe.py28
-rw-r--r--lib/Python/Lib/OpenSSL/version.py9
-rw-r--r--lib/Python/Lib/PIL/BdfFontFile.py133
-rw-r--r--lib/Python/Lib/PIL/BmpImagePlugin.py281
-rw-r--r--lib/Python/Lib/PIL/BufrStubImagePlugin.py72
-rw-r--r--lib/Python/Lib/PIL/ContainerIO.py117
-rw-r--r--lib/Python/Lib/PIL/CurImagePlugin.py87
-rw-r--r--lib/Python/Lib/PIL/DcxImagePlugin.py79
-rw-r--r--lib/Python/Lib/PIL/EpsImagePlugin.py424
-rw-r--r--lib/Python/Lib/PIL/ExifTags.py193
-rw-r--r--lib/Python/Lib/PIL/FitsStubImagePlugin.py73
-rw-r--r--lib/Python/Lib/PIL/FliImagePlugin.py143
-rw-r--r--lib/Python/Lib/PIL/FontFile.py119
-rw-r--r--lib/Python/Lib/PIL/FpxImagePlugin.py227
-rw-r--r--lib/Python/Lib/PIL/GbrImagePlugin.py71
-rw-r--r--lib/Python/Lib/PIL/GdImageFile.py92
-rw-r--r--lib/Python/Lib/PIL/GifImagePlugin.py543
-rw-r--r--lib/Python/Lib/PIL/GimpGradientFile.py137
-rw-r--r--lib/Python/Lib/PIL/GimpPaletteFile.py62
-rw-r--r--lib/Python/Lib/PIL/GribStubImagePlugin.py72
-rw-r--r--lib/Python/Lib/PIL/Hdf5StubImagePlugin.py73
-rw-r--r--lib/Python/Lib/PIL/IcnsImagePlugin.py311
-rw-r--r--lib/Python/Lib/PIL/IcoImagePlugin.py284
-rw-r--r--lib/Python/Lib/PIL/ImImagePlugin.py347
-rw-r--r--lib/Python/Lib/PIL/Image.py2483
-rw-r--r--lib/Python/Lib/PIL/ImageChops.py283
-rw-r--r--lib/Python/Lib/PIL/ImageCms.py972
-rw-r--r--lib/Python/Lib/PIL/ImageColor.py279
-rw-r--r--lib/Python/Lib/PIL/ImageDraw.py383
-rw-r--r--lib/Python/Lib/PIL/ImageDraw2.py111
-rw-r--r--lib/Python/Lib/PIL/ImageEnhance.py99
-rw-r--r--lib/Python/Lib/PIL/ImageFile.py526
-rw-r--r--lib/Python/Lib/PIL/ImageFileIO.py40
-rw-r--r--lib/Python/Lib/PIL/ImageFilter.py275
-rw-r--r--lib/Python/Lib/PIL/ImageFont.py459
-rw-r--r--lib/Python/Lib/PIL/ImageGrab.py52
-rw-r--r--lib/Python/Lib/PIL/ImageMath.py270
-rw-r--r--lib/Python/Lib/PIL/ImageMode.py52
-rw-r--r--lib/Python/Lib/PIL/ImageMorph.py245
-rw-r--r--lib/Python/Lib/PIL/ImageOps.py462
-rw-r--r--lib/Python/Lib/PIL/ImagePalette.py235
-rw-r--r--lib/Python/Lib/PIL/ImagePath.py66
-rw-r--r--lib/Python/Lib/PIL/ImageQt.py98
-rw-r--r--lib/Python/Lib/PIL/ImageSequence.py42
-rw-r--r--lib/Python/Lib/PIL/ImageShow.py179
-rw-r--r--lib/Python/Lib/PIL/ImageStat.py147
-rw-r--r--lib/Python/Lib/PIL/ImageTk.py292
-rw-r--r--lib/Python/Lib/PIL/ImageTransform.py103
-rw-r--r--lib/Python/Lib/PIL/ImageWin.py251
-rw-r--r--lib/Python/Lib/PIL/ImtImagePlugin.py94
-rw-r--r--lib/Python/Lib/PIL/IptcImagePlugin.py268
-rw-r--r--lib/Python/Lib/PIL/Jpeg2KImagePlugin.py276
-rw-r--r--lib/Python/Lib/PIL/JpegImagePlugin.py739
-rw-r--r--lib/Python/Lib/PIL/JpegPresets.py241
-rw-r--r--lib/Python/Lib/PIL/McIdasImagePlugin.py74
-rw-r--r--lib/Python/Lib/PIL/MicImagePlugin.py96
-rw-r--r--lib/Python/Lib/PIL/MpegImagePlugin.py85
-rw-r--r--lib/Python/Lib/PIL/MpoImagePlugin.py90
-rw-r--r--lib/Python/Lib/PIL/MspImagePlugin.py104
-rw-r--r--lib/Python/Lib/PIL/OleFileIO-README.md351
-rw-r--r--lib/Python/Lib/PIL/OleFileIO.py2087
-rw-r--r--lib/Python/Lib/PIL/PSDraw.py237
-rw-r--r--lib/Python/Lib/PIL/PaletteFile.py55
-rw-r--r--lib/Python/Lib/PIL/PalmImagePlugin.py240
-rw-r--r--lib/Python/Lib/PIL/PcdImagePlugin.py79
-rw-r--r--lib/Python/Lib/PIL/PcfFontFile.py252
-rw-r--r--lib/Python/Lib/PIL/PcxImagePlugin.py186
-rw-r--r--lib/Python/Lib/PIL/PdfImagePlugin.py238
-rw-r--r--lib/Python/Lib/PIL/PixarImagePlugin.py69
-rw-r--r--lib/Python/Lib/PIL/PngImagePlugin.py807
-rw-r--r--lib/Python/Lib/PIL/PpmImagePlugin.py172
-rw-r--r--lib/Python/Lib/PIL/PsdImagePlugin.py304
-rw-r--r--lib/Python/Lib/PIL/PyAccess.py315
-rw-r--r--lib/Python/Lib/PIL/SgiImagePlugin.py91
-rw-r--r--lib/Python/Lib/PIL/SpiderImagePlugin.py312
-rw-r--r--lib/Python/Lib/PIL/SunImagePlugin.py83
-rw-r--r--lib/Python/Lib/PIL/TarIO.py57
-rw-r--r--lib/Python/Lib/PIL/TgaImagePlugin.py199
-rw-r--r--lib/Python/Lib/PIL/TiffImagePlugin.py1225
-rw-r--r--lib/Python/Lib/PIL/TiffTags.py307
-rw-r--r--lib/Python/Lib/PIL/WalImageFile.py131
-rw-r--r--lib/Python/Lib/PIL/WebPImagePlugin.py80
-rw-r--r--lib/Python/Lib/PIL/WmfImagePlugin.py173
-rw-r--r--lib/Python/Lib/PIL/XVThumbImagePlugin.py75
-rw-r--r--lib/Python/Lib/PIL/XbmImagePlugin.py96
-rw-r--r--lib/Python/Lib/PIL/XpmImagePlugin.py131
-rw-r--r--lib/Python/Lib/PIL/__init__.py58
-rw-r--r--lib/Python/Lib/PIL/_binary.py76
-rw-r--r--lib/Python/Lib/PIL/_util.py27
-rw-r--r--lib/Python/Lib/SafeEval.py (renamed from module/lib/SafeEval.py)0
-rw-r--r--lib/Python/Lib/Unzip.py (renamed from module/lib/Unzip.py)0
-rw-r--r--lib/Python/Lib/__init__.py (renamed from module/lib/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/__init__.py (renamed from module/lib/beaker/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/cache.py (renamed from module/lib/beaker/cache.py)0
-rw-r--r--lib/Python/Lib/beaker/container.py (renamed from module/lib/beaker/container.py)0
-rw-r--r--lib/Python/Lib/beaker/converters.py (renamed from module/lib/beaker/converters.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/__init__.py (renamed from module/lib/beaker/crypto/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/jcecrypto.py (renamed from module/lib/beaker/crypto/jcecrypto.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/pbkdf2.py (renamed from module/lib/beaker/crypto/pbkdf2.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/pycrypto.py (renamed from module/lib/beaker/crypto/pycrypto.py)0
-rw-r--r--lib/Python/Lib/beaker/crypto/util.py (renamed from module/lib/beaker/crypto/util.py)0
-rw-r--r--lib/Python/Lib/beaker/exceptions.py (renamed from module/lib/beaker/exceptions.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/__init__.py (renamed from module/lib/beaker/ext/__init__.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/database.py (renamed from module/lib/beaker/ext/database.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/google.py (renamed from module/lib/beaker/ext/google.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/memcached.py (renamed from module/lib/beaker/ext/memcached.py)0
-rw-r--r--lib/Python/Lib/beaker/ext/sqla.py (renamed from module/lib/beaker/ext/sqla.py)0
-rw-r--r--lib/Python/Lib/beaker/middleware.py (renamed from module/lib/beaker/middleware.py)0
-rw-r--r--lib/Python/Lib/beaker/session.py (renamed from module/lib/beaker/session.py)0
-rw-r--r--lib/Python/Lib/beaker/synchronization.py (renamed from module/lib/beaker/synchronization.py)0
-rw-r--r--lib/Python/Lib/beaker/util.py (renamed from module/lib/beaker/util.py)0
-rw-r--r--lib/Python/Lib/bitmath/__init__.py1261
-rw-r--r--lib/Python/Lib/bitmath/integrations.py104
-rw-r--r--lib/Python/Lib/bottle.py (renamed from module/lib/bottle.py)0
-rw-r--r--lib/Python/Lib/colorama/__init__.py7
-rw-r--r--lib/Python/Lib/colorama/ansi.py99
-rw-r--r--lib/Python/Lib/colorama/ansitowin32.py228
-rw-r--r--lib/Python/Lib/colorama/initialise.py66
-rw-r--r--lib/Python/Lib/colorama/win32.py146
-rw-r--r--lib/Python/Lib/colorama/winterm.py151
-rw-r--r--lib/Python/Lib/colorlog/__init__.py14
-rw-r--r--lib/Python/Lib/colorlog/colorlog.py137
-rw-r--r--lib/Python/Lib/colorlog/escape_codes.py57
-rw-r--r--lib/Python/Lib/colorlog/logging.py44
-rw-r--r--lib/Python/Lib/jinja2/__init__.py (renamed from module/lib/jinja2/__init__.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/__init__.py (renamed from module/lib/jinja2/_markupsafe/__init__.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/_bundle.py (renamed from module/lib/jinja2/_markupsafe/_bundle.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/_constants.py (renamed from module/lib/jinja2/_markupsafe/_constants.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/_native.py (renamed from module/lib/jinja2/_markupsafe/_native.py)0
-rw-r--r--lib/Python/Lib/jinja2/_markupsafe/tests.py (renamed from module/lib/jinja2/_markupsafe/tests.py)0
-rw-r--r--lib/Python/Lib/jinja2/_stringdefs.py (renamed from module/lib/jinja2/_stringdefs.py)0
-rw-r--r--lib/Python/Lib/jinja2/bccache.py (renamed from module/lib/jinja2/bccache.py)0
-rw-r--r--lib/Python/Lib/jinja2/compiler.py (renamed from module/lib/jinja2/compiler.py)0
-rw-r--r--lib/Python/Lib/jinja2/constants.py (renamed from module/lib/jinja2/constants.py)0
-rw-r--r--lib/Python/Lib/jinja2/debug.py (renamed from module/lib/jinja2/debug.py)0
-rw-r--r--lib/Python/Lib/jinja2/defaults.py (renamed from module/lib/jinja2/defaults.py)0
-rw-r--r--lib/Python/Lib/jinja2/environment.py (renamed from module/lib/jinja2/environment.py)0
-rw-r--r--lib/Python/Lib/jinja2/exceptions.py (renamed from module/lib/jinja2/exceptions.py)0
-rw-r--r--lib/Python/Lib/jinja2/ext.py (renamed from module/lib/jinja2/ext.py)0
-rw-r--r--lib/Python/Lib/jinja2/filters.py (renamed from module/lib/jinja2/filters.py)0
-rw-r--r--lib/Python/Lib/jinja2/lexer.py (renamed from module/lib/jinja2/lexer.py)0
-rw-r--r--lib/Python/Lib/jinja2/loaders.py (renamed from module/lib/jinja2/loaders.py)0
-rw-r--r--lib/Python/Lib/jinja2/meta.py (renamed from module/lib/jinja2/meta.py)0
-rw-r--r--lib/Python/Lib/jinja2/nodes.py (renamed from module/lib/jinja2/nodes.py)0
-rw-r--r--lib/Python/Lib/jinja2/optimizer.py (renamed from module/lib/jinja2/optimizer.py)0
-rw-r--r--lib/Python/Lib/jinja2/parser.py (renamed from module/lib/jinja2/parser.py)0
-rw-r--r--lib/Python/Lib/jinja2/runtime.py (renamed from module/lib/jinja2/runtime.py)0
-rw-r--r--lib/Python/Lib/jinja2/sandbox.py (renamed from module/lib/jinja2/sandbox.py)0
-rw-r--r--lib/Python/Lib/jinja2/tests.py (renamed from module/lib/jinja2/tests.py)0
-rw-r--r--lib/Python/Lib/jinja2/utils.py (renamed from module/lib/jinja2/utils.py)0
-rw-r--r--lib/Python/Lib/jinja2/visitor.py (renamed from module/lib/jinja2/visitor.py)0
-rw-r--r--lib/Python/Lib/rename_process.py (renamed from module/lib/rename_process.py)0
-rw-r--r--lib/Python/Lib/send2trash/__init__.py19
-rw-r--r--lib/Python/Lib/send2trash/compat.py13
-rw-r--r--lib/Python/Lib/send2trash/plat_gio.py14
-rw-r--r--lib/Python/Lib/send2trash/plat_osx.py48
-rw-r--r--lib/Python/Lib/send2trash/plat_other.py160
-rw-r--r--lib/Python/Lib/send2trash/plat_win.py59
-rw-r--r--lib/Python/Lib/simplejson/__init__.py (renamed from module/lib/simplejson/__init__.py)0
-rw-r--r--lib/Python/Lib/simplejson/decoder.py (renamed from module/lib/simplejson/decoder.py)0
-rw-r--r--lib/Python/Lib/simplejson/encoder.py (renamed from module/lib/simplejson/encoder.py)0
-rw-r--r--lib/Python/Lib/simplejson/ordered_dict.py (renamed from module/lib/simplejson/ordered_dict.py)0
-rw-r--r--lib/Python/Lib/simplejson/scanner.py (renamed from module/lib/simplejson/scanner.py)0
-rw-r--r--lib/Python/Lib/simplejson/tool.py (renamed from module/lib/simplejson/tool.py)0
-rw-r--r--lib/Python/Lib/thrift/TSCons.py (renamed from module/lib/thrift/TSCons.py)0
-rw-r--r--lib/Python/Lib/thrift/TSerialization.py (renamed from module/lib/thrift/TSerialization.py)0
-rw-r--r--lib/Python/Lib/thrift/Thrift.py (renamed from module/lib/thrift/Thrift.py)0
-rw-r--r--lib/Python/Lib/thrift/__init__.py (renamed from module/lib/thrift/__init__.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TBase.py (renamed from module/lib/thrift/protocol/TBase.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TBinaryProtocol.py (renamed from module/lib/thrift/protocol/TBinaryProtocol.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TCompactProtocol.py (renamed from module/lib/thrift/protocol/TCompactProtocol.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/TProtocol.py (renamed from module/lib/thrift/protocol/TProtocol.py)0
-rw-r--r--lib/Python/Lib/thrift/protocol/__init__.py (renamed from module/lib/thrift/protocol/__init__.py)0
-rw-r--r--lib/Python/Lib/thrift/server/THttpServer.py (renamed from module/lib/thrift/server/THttpServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/TNonblockingServer.py (renamed from module/lib/thrift/server/TNonblockingServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/TProcessPoolServer.py (renamed from module/lib/thrift/server/TProcessPoolServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/TServer.py (renamed from module/lib/thrift/server/TServer.py)0
-rw-r--r--lib/Python/Lib/thrift/server/__init__.py (renamed from module/lib/thrift/server/__init__.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/THttpClient.py (renamed from module/lib/thrift/transport/THttpClient.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TSocket.py (renamed from module/lib/thrift/transport/TSocket.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TTransport.py (renamed from module/lib/thrift/transport/TTransport.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TTwisted.py (renamed from module/lib/thrift/transport/TTwisted.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/TZlibTransport.py (renamed from module/lib/thrift/transport/TZlibTransport.py)0
-rw-r--r--lib/Python/Lib/thrift/transport/__init__.py (renamed from module/lib/thrift/transport/__init__.py)0
-rw-r--r--lib/Python/Lib/wsgiserver/LICENSE.txt (renamed from module/lib/wsgiserver/LICENSE.txt)0
-rw-r--r--lib/Python/Lib/wsgiserver/__init__.py (renamed from module/lib/wsgiserver/__init__.py)0
-rw-r--r--locale/cli.pot299
-rw-r--r--locale/core.pot866
-rw-r--r--locale/crowdin.yaml12
-rw-r--r--locale/cs/LC_MESSAGES/django.mobin8921 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/pyLoad.mobin17425 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/pyLoadCli.mobin4985 -> 0 bytes
-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/de/LC_MESSAGES/django.mobin9080 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/pyLoad.mobin18222 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/pyLoadCli.mobin5266 -> 0 bytes
-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/django.pot686
-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/es/LC_MESSAGES/django.mobin9001 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/pyLoad.mobin17919 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/pyLoadCli.mobin5139 -> 0 bytes
-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/fi/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/pyLoadCli.mobin375 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/django.mobin8407 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/pyLoad.mobin17270 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/pyLoadCli.mobin5363 -> 0 bytes
-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/gui.pot511
-rw-r--r--locale/it/LC_MESSAGES/django.mobin8770 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/pyLoad.mobin16689 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/pyLoadCli.mobin5113 -> 0 bytes
-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/nl/LC_MESSAGES/django.mobin7957 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/pyLoad.mobin16191 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/pyLoadCli.mobin5102 -> 0 bytes
-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/pavement.py423
-rw-r--r--locale/pl/LC_MESSAGES/django.mobin7971 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/pyLoad.mobin16488 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/pyLoadCli.mobin5119 -> 0 bytes
-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/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/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/pyLoadCli.mobin375 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/django.mobin9739 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/pyLoad.mobin15299 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/pyLoadCli.mobin4299 -> 0 bytes
-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/setup.pot495
-rw-r--r--locale/sr/LC_MESSAGES/django.mobin9128 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/pyLoad.mobin19506 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/pyLoadCli.mobin6004 -> 0 bytes
-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/sv/LC_MESSAGES/django.mobin5358 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/pyLoad.mobin6860 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/pyLoadCli.mobin3366 -> 0 bytes
-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/tr/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/pyLoadCli.mobin1298 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-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/feedparser.py3885
-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/network/__init__.py1
-rw-r--r--module/plugins/Account.py292
-rw-r--r--module/plugins/AccountManager.py185
-rw-r--r--module/plugins/Container.py75
-rw-r--r--module/plugins/Crypter.py72
-rw-r--r--module/plugins/Hook.py161
-rw-r--r--module/plugins/Hoster.py33
-rw-r--r--module/plugins/Plugin.py617
-rw-r--r--module/plugins/PluginManager.py380
-rw-r--r--module/plugins/__init__.py0
-rw-r--r--module/plugins/accounts/AlldebridCom.py63
-rw-r--r--module/plugins/accounts/BackinNet.py16
-rw-r--r--module/plugins/accounts/BitshareCom.py34
-rw-r--r--module/plugins/accounts/CatShareNet.py61
-rw-r--r--module/plugins/accounts/CloudzillaTo.py37
-rw-r--r--module/plugins/accounts/CramitIn.py16
-rw-r--r--module/plugins/accounts/CzshareCom.py54
-rw-r--r--module/plugins/accounts/DebridItaliaCom.py44
-rw-r--r--module/plugins/accounts/DepositfilesCom.py36
-rw-r--r--module/plugins/accounts/EasybytezCom.py19
-rw-r--r--module/plugins/accounts/EuroshareEu.py41
-rw-r--r--module/plugins/accounts/ExashareCom.py16
-rw-r--r--module/plugins/accounts/FastixRu.py41
-rw-r--r--module/plugins/accounts/FastshareCz.py50
-rw-r--r--module/plugins/accounts/File4SafeCom.py18
-rw-r--r--module/plugins/accounts/FileParadoxIn.py16
-rw-r--r--module/plugins/accounts/FilecloudIo.py59
-rw-r--r--module/plugins/accounts/FilefactoryCom.py48
-rw-r--r--module/plugins/accounts/FilejungleCom.py50
-rw-r--r--module/plugins/accounts/FileomCom.py16
-rw-r--r--module/plugins/accounts/FilerNet.py59
-rw-r--r--module/plugins/accounts/FilerioCom.py16
-rw-r--r--module/plugins/accounts/FilesMailRu.py31
-rw-r--r--module/plugins/accounts/FileserveCom.py44
-rw-r--r--module/plugins/accounts/FourSharedCom.py34
-rw-r--r--module/plugins/accounts/FreakshareCom.py51
-rw-r--r--module/plugins/accounts/FreeWayMe.py52
-rw-r--r--module/plugins/accounts/FshareVn.py62
-rw-r--r--module/plugins/accounts/Ftp.py17
-rw-r--r--module/plugins/accounts/HellshareCz.py79
-rw-r--r--module/plugins/accounts/Http.py17
-rw-r--r--module/plugins/accounts/HugefilesNet.py16
-rw-r--r--module/plugins/accounts/HundredEightyUploadCom.py16
-rw-r--r--module/plugins/accounts/JunkyvideoCom.py16
-rw-r--r--module/plugins/accounts/JunocloudMe.py16
-rw-r--r--module/plugins/accounts/Keep2ShareCc.py73
-rw-r--r--module/plugins/accounts/LetitbitNet.py34
-rw-r--r--module/plugins/accounts/LinestorageCom.py17
-rw-r--r--module/plugins/accounts/LinksnappyCom.py57
-rw-r--r--module/plugins/accounts/MegaDebridEu.py39
-rw-r--r--module/plugins/accounts/MegaRapidCz.py60
-rw-r--r--module/plugins/accounts/MegaRapidoNet.py57
-rw-r--r--module/plugins/accounts/MegasharesCom.py48
-rw-r--r--module/plugins/accounts/MovReelCom.py19
-rw-r--r--module/plugins/accounts/MultihostersCom.py16
-rw-r--r--module/plugins/accounts/MultishareCz.py44
-rw-r--r--module/plugins/accounts/MyfastfileCom.py37
-rw-r--r--module/plugins/accounts/NetloadIn.py63
-rw-r--r--module/plugins/accounts/NoPremiumPl.py81
-rw-r--r--module/plugins/accounts/NosuploadCom.py16
-rw-r--r--module/plugins/accounts/NovafileCom.py16
-rw-r--r--module/plugins/accounts/NowVideoSx.py56
-rw-r--r--module/plugins/accounts/OboomCom.py77
-rw-r--r--module/plugins/accounts/OneFichierCom.py58
-rw-r--r--module/plugins/accounts/OverLoadMe.py43
-rw-r--r--module/plugins/accounts/PremiumTo.py37
-rw-r--r--module/plugins/accounts/PremiumizeMe.py49
-rw-r--r--module/plugins/accounts/PutdriveCom.py16
-rw-r--r--module/plugins/accounts/QuickshareCz.py43
-rw-r--r--module/plugins/accounts/RPNetBiz.py51
-rw-r--r--module/plugins/accounts/RapideoPl.py80
-rw-r--r--module/plugins/accounts/RapidfileshareNet.py18
-rw-r--r--module/plugins/accounts/RapidgatorNet.py72
-rw-r--r--module/plugins/accounts/RapiduNet.py65
-rw-r--r--module/plugins/accounts/RarefileNet.py16
-rw-r--r--module/plugins/accounts/RealdebridCom.py40
-rw-r--r--module/plugins/accounts/RehostTo.py54
-rw-r--r--module/plugins/accounts/RyushareCom.py16
-rw-r--r--module/plugins/accounts/SafesharingEu.py16
-rw-r--r--module/plugins/accounts/SecureUploadEu.py16
-rw-r--r--module/plugins/accounts/SendmywayCom.py16
-rw-r--r--module/plugins/accounts/ShareonlineBiz.py68
-rw-r--r--module/plugins/accounts/SimplyPremiumCom.py48
-rw-r--r--module/plugins/accounts/SimplydebridCom.py35
-rw-r--r--module/plugins/accounts/SmoozedCom.py78
-rw-r--r--module/plugins/accounts/StahnuTo.py35
-rw-r--r--module/plugins/accounts/StreamcloudEu.py16
-rw-r--r--module/plugins/accounts/TurbobitNet.py43
-rw-r--r--module/plugins/accounts/TusfilesNet.py22
-rw-r--r--module/plugins/accounts/UlozTo.py49
-rw-r--r--module/plugins/accounts/UploadableCh.py34
-rw-r--r--module/plugins/accounts/UploadcCom.py16
-rw-r--r--module/plugins/accounts/UploadedTo.py71
-rw-r--r--module/plugins/accounts/UploadheroCom.py42
-rw-r--r--module/plugins/accounts/UploadingCom.py65
-rw-r--r--module/plugins/accounts/UptoboxCom.py18
-rw-r--r--module/plugins/accounts/VidPlayNet.py16
-rw-r--r--module/plugins/accounts/WebshareCz.py68
-rw-r--r--module/plugins/accounts/XFileSharingPro.py34
-rw-r--r--module/plugins/accounts/YibaishiwuCom.py40
-rw-r--r--module/plugins/accounts/ZeveraCom.py78
-rw-r--r--module/plugins/accounts/__init__.py0
-rw-r--r--module/plugins/captcha/GigasizeCom.py24
-rw-r--r--module/plugins/captcha/LinksaveIn.py157
-rw-r--r--module/plugins/captcha/NetloadIn.py29
-rw-r--r--module/plugins/captcha/OCR.py319
-rw-r--r--module/plugins/captcha/ShareonlineBiz.py39
-rw-r--r--module/plugins/captcha/__init__.py0
-rw-r--r--module/plugins/container/CCF.py47
-rw-r--r--module/plugins/container/DLC.py72
-rw-r--r--module/plugins/container/RSDF.py61
-rw-r--r--module/plugins/container/TXT.py69
-rw-r--r--module/plugins/container/__init__.py0
-rw-r--r--module/plugins/crypter/BitshareComFolder.py25
-rw-r--r--module/plugins/crypter/C1NeonCom.py19
-rw-r--r--module/plugins/crypter/ChipDe.py29
-rw-r--r--module/plugins/crypter/CloudzillaToFolder.py38
-rw-r--r--module/plugins/crypter/CrockoComFolder.py24
-rw-r--r--module/plugins/crypter/CryptItCom.py19
-rw-r--r--module/plugins/crypter/CzshareComFolder.py32
-rw-r--r--module/plugins/crypter/DailymotionComFolder.py105
-rw-r--r--module/plugins/crypter/DataHuFolder.py44
-rw-r--r--module/plugins/crypter/DdlstorageComFolder.py20
-rw-r--r--module/plugins/crypter/DepositfilesComFolder.py24
-rw-r--r--module/plugins/crypter/Dereferer.py17
-rw-r--r--module/plugins/crypter/DevhostStFolder.py65
-rw-r--r--module/plugins/crypter/DlProtectCom.py69
-rw-r--r--module/plugins/crypter/DontKnowMe.py17
-rw-r--r--module/plugins/crypter/DuckCryptInfo.py59
-rw-r--r--module/plugins/crypter/DuploadOrgFolder.py19
-rw-r--r--module/plugins/crypter/EasybytezComFolder.py23
-rw-r--r--module/plugins/crypter/EmbeduploadCom.py60
-rw-r--r--module/plugins/crypter/FilebeerInfoFolder.py19
-rw-r--r--module/plugins/crypter/FilecloudIoFolder.py25
-rw-r--r--module/plugins/crypter/FilecryptCc.py179
-rw-r--r--module/plugins/crypter/FilefactoryComFolder.py32
-rw-r--r--module/plugins/crypter/FilerNetFolder.py28
-rw-r--r--module/plugins/crypter/FileserveComFolder.py38
-rw-r--r--module/plugins/crypter/FilesonicComFolder.py19
-rw-r--r--module/plugins/crypter/FilestubeCom.py25
-rw-r--r--module/plugins/crypter/FiletramCom.py26
-rw-r--r--module/plugins/crypter/FiredriveComFolder.py19
-rw-r--r--module/plugins/crypter/FourChanOrg.py27
-rw-r--r--module/plugins/crypter/FreakhareComFolder.py42
-rw-r--r--module/plugins/crypter/FreetexthostCom.py31
-rw-r--r--module/plugins/crypter/FshareVnFolder.py24
-rw-r--r--module/plugins/crypter/Go4UpCom.py51
-rw-r--r--module/plugins/crypter/GooGl.py32
-rw-r--r--module/plugins/crypter/HoerbuchIn.py62
-rw-r--r--module/plugins/crypter/HotfileComFolder.py19
-rw-r--r--module/plugins/crypter/ILoadTo.py19
-rw-r--r--module/plugins/crypter/ImgurComAlbum.py31
-rw-r--r--module/plugins/crypter/LetitbitNetFolder.py33
-rw-r--r--module/plugins/crypter/LinkCryptWs.py322
-rw-r--r--module/plugins/crypter/LinkSaveIn.py22
-rw-r--r--module/plugins/crypter/LinkdecrypterCom.py69
-rw-r--r--module/plugins/crypter/LixIn.py62
-rw-r--r--module/plugins/crypter/LofCc.py19
-rw-r--r--module/plugins/crypter/MBLinkInfo.py20
-rw-r--r--module/plugins/crypter/MediafireComFolder.py58
-rw-r--r--module/plugins/crypter/MegaCoNzFolder.py32
-rw-r--r--module/plugins/crypter/MegaRapidCzFolder.py24
-rw-r--r--module/plugins/crypter/MegauploadComFolder.py19
-rw-r--r--module/plugins/crypter/Movie2KTo.py19
-rw-r--r--module/plugins/crypter/MultiUpOrg.py42
-rw-r--r--module/plugins/crypter/MultiloadCz.py42
-rw-r--r--module/plugins/crypter/MultiuploadCom.py19
-rw-r--r--module/plugins/crypter/NCryptIn.py310
-rw-r--r--module/plugins/crypter/NetfolderIn.py74
-rw-r--r--module/plugins/crypter/NosvideoCom.py25
-rw-r--r--module/plugins/crypter/OneKhDe.py42
-rw-r--r--module/plugins/crypter/OronComFolder.py19
-rw-r--r--module/plugins/crypter/PastebinCom.py25
-rw-r--r--module/plugins/crypter/QuickshareCzFolder.py31
-rw-r--r--module/plugins/crypter/RSLayerCom.py19
-rw-r--r--module/plugins/crypter/RelinkUs.py293
-rw-r--r--module/plugins/crypter/SafelinkingNet.py81
-rw-r--r--module/plugins/crypter/SecuredIn.py19
-rw-r--r--module/plugins/crypter/SexuriaCom.py94
-rw-r--r--module/plugins/crypter/ShareLinksBiz.py279
-rw-r--r--module/plugins/crypter/SharingmatrixComFolder.py19
-rw-r--r--module/plugins/crypter/SpeedLoadOrgFolder.py19
-rw-r--r--module/plugins/crypter/StealthTo.py19
-rw-r--r--module/plugins/crypter/TnyCz.py31
-rw-r--r--module/plugins/crypter/TrailerzoneInfo.py19
-rw-r--r--module/plugins/crypter/TurbobitNetFolder.py48
-rw-r--r--module/plugins/crypter/TusfilesNetFolder.py46
-rw-r--r--module/plugins/crypter/UlozToFolder.py46
-rw-r--r--module/plugins/crypter/UploadableChFolder.py28
-rw-r--r--module/plugins/crypter/UploadedToFolder.py37
-rw-r--r--module/plugins/crypter/WiiReloadedOrg.py19
-rw-r--r--module/plugins/crypter/WuploadComFolder.py19
-rw-r--r--module/plugins/crypter/XFileSharingProFolder.py52
-rw-r--r--module/plugins/crypter/XupPl.py25
-rw-r--r--module/plugins/crypter/YoutubeComFolder.py147
-rw-r--r--module/plugins/crypter/__init__.py0
-rw-r--r--module/plugins/hooks/AlldebridComHook.py27
-rw-r--r--module/plugins/hooks/AndroidPhoneNotify.py110
-rw-r--r--module/plugins/hooks/AntiVirus.py119
-rw-r--r--module/plugins/hooks/BypassCaptcha.py141
-rw-r--r--module/plugins/hooks/Captcha9Kw.py254
-rw-r--r--module/plugins/hooks/CaptchaBrotherhood.py172
-rw-r--r--module/plugins/hooks/Checksum.py196
-rw-r--r--module/plugins/hooks/ClickAndLoad.py114
-rw-r--r--module/plugins/hooks/DeathByCaptcha.py220
-rw-r--r--module/plugins/hooks/DebridItaliaComHook.py26
-rw-r--r--module/plugins/hooks/DeleteFinished.py81
-rw-r--r--module/plugins/hooks/DownloadScheduler.py81
-rw-r--r--module/plugins/hooks/EasybytezComHook.py30
-rw-r--r--module/plugins/hooks/ExpertDecoders.py103
-rw-r--r--module/plugins/hooks/ExternalScripts.py217
-rw-r--r--module/plugins/hooks/ExtractArchive.py571
-rw-r--r--module/plugins/hooks/FastixRuHook.py29
-rw-r--r--module/plugins/hooks/FreeWayMeHook.py32
-rw-r--r--module/plugins/hooks/HotFolder.py71
-rw-r--r--module/plugins/hooks/IRCInterface.py433
-rw-r--r--module/plugins/hooks/ImageTyperz.py159
-rw-r--r--module/plugins/hooks/JustPremium.py56
-rw-r--r--module/plugins/hooks/LinkdecrypterComHook.py32
-rw-r--r--module/plugins/hooks/LinksnappyComHook.py27
-rw-r--r--module/plugins/hooks/MegaDebridEuHook.py33
-rw-r--r--module/plugins/hooks/MegaRapidoNetHook.py81
-rw-r--r--module/plugins/hooks/MergeFiles.py84
-rw-r--r--module/plugins/hooks/MultiHome.py88
-rw-r--r--module/plugins/hooks/MultihostersComHook.py18
-rw-r--r--module/plugins/hooks/MultishareCzHook.py29
-rw-r--r--module/plugins/hooks/MyfastfileComHook.py28
-rw-r--r--module/plugins/hooks/NoPremiumPlHook.py29
-rw-r--r--module/plugins/hooks/OverLoadMeHook.py29
-rw-r--r--module/plugins/hooks/PremiumToHook.py27
-rw-r--r--module/plugins/hooks/PremiumizeMeHook.py38
-rw-r--r--module/plugins/hooks/PutdriveComHook.py18
-rw-r--r--module/plugins/hooks/RPNetBizHook.py36
-rw-r--r--module/plugins/hooks/RapideoPlHook.py29
-rw-r--r--module/plugins/hooks/RealdebridComHook.py27
-rw-r--r--module/plugins/hooks/RehostToHook.py27
-rw-r--r--module/plugins/hooks/RestartFailed.py46
-rw-r--r--module/plugins/hooks/SimplyPremiumComHook.py29
-rw-r--r--module/plugins/hooks/SimplydebridComHook.py24
-rw-r--r--module/plugins/hooks/SkipRev.py112
-rw-r--r--module/plugins/hooks/SmoozedComHook.py24
-rw-r--r--module/plugins/hooks/UnSkipOnFail.py97
-rw-r--r--module/plugins/hooks/UpdateManager.py329
-rw-r--r--module/plugins/hooks/UserAgentSwitcher.py47
-rw-r--r--module/plugins/hooks/WindowsPhoneNotify.py126
-rw-r--r--module/plugins/hooks/XFileSharingPro.py132
-rw-r--r--module/plugins/hooks/XMPPInterface.py252
-rw-r--r--module/plugins/hooks/ZeveraComHook.py25
-rw-r--r--module/plugins/hooks/__init__.py0
-rw-r--r--module/plugins/hoster/AlldebridCom.py54
-rw-r--r--module/plugins/hoster/AndroidfilehostCom.py64
-rw-r--r--module/plugins/hoster/BasePlugin.py103
-rw-r--r--module/plugins/hoster/BasketbuildCom.py62
-rw-r--r--module/plugins/hoster/BayfilesCom.py19
-rw-r--r--module/plugins/hoster/BezvadataCz.py95
-rw-r--r--module/plugins/hoster/BillionuploadsCom.py18
-rw-r--r--module/plugins/hoster/BitshareCom.py159
-rw-r--r--module/plugins/hoster/BoltsharingCom.py19
-rw-r--r--module/plugins/hoster/CatShareNet.py62
-rw-r--r--module/plugins/hoster/CloudzerNet.py21
-rw-r--r--module/plugins/hoster/CloudzillaTo.py62
-rw-r--r--module/plugins/hoster/CramitIn.py23
-rw-r--r--module/plugins/hoster/CrockoCom.py67
-rw-r--r--module/plugins/hoster/CyberlockerCh.py19
-rw-r--r--module/plugins/hoster/CzshareCom.py162
-rw-r--r--module/plugins/hoster/DailymotionCom.py125
-rw-r--r--module/plugins/hoster/DataHu.py35
-rw-r--r--module/plugins/hoster/DataportCz.py58
-rw-r--r--module/plugins/hoster/DateiTo.py83
-rw-r--r--module/plugins/hoster/DdlstorageCom.py20
-rw-r--r--module/plugins/hoster/DebridItaliaCom.py44
-rw-r--r--module/plugins/hoster/DepositfilesCom.py92
-rw-r--r--module/plugins/hoster/DevhostSt.py37
-rw-r--r--module/plugins/hoster/DlFreeFr.py140
-rw-r--r--module/plugins/hoster/DodanePl.py19
-rw-r--r--module/plugins/hoster/DropboxCom.py39
-rw-r--r--module/plugins/hoster/DuploadOrg.py19
-rw-r--r--module/plugins/hoster/EasybytezCom.py24
-rw-r--r--module/plugins/hoster/EdiskCz.py57
-rw-r--r--module/plugins/hoster/EgoFilesCom.py19
-rw-r--r--module/plugins/hoster/EnteruploadCom.py19
-rw-r--r--module/plugins/hoster/EpicShareNet.py19
-rw-r--r--module/plugins/hoster/EuroshareEu.py68
-rw-r--r--module/plugins/hoster/ExashareCom.py38
-rw-r--r--module/plugins/hoster/ExtabitCom.py78
-rw-r--r--module/plugins/hoster/FastixRu.py44
-rw-r--r--module/plugins/hoster/FastshareCz.py80
-rw-r--r--module/plugins/hoster/FileApeCom.py19
-rw-r--r--module/plugins/hoster/FileSharkPl.py117
-rw-r--r--module/plugins/hoster/FileStoreTo.py38
-rw-r--r--module/plugins/hoster/FilebeerInfo.py19
-rw-r--r--module/plugins/hoster/FilecloudIo.py126
-rw-r--r--module/plugins/hoster/FilefactoryCom.py85
-rw-r--r--module/plugins/hoster/FilejungleCom.py29
-rw-r--r--module/plugins/hoster/FileomCom.py33
-rw-r--r--module/plugins/hoster/FilepostCom.py124
-rw-r--r--module/plugins/hoster/FilepupNet.py48
-rw-r--r--module/plugins/hoster/FilerNet.py66
-rw-r--r--module/plugins/hoster/FilerioCom.py23
-rw-r--r--module/plugins/hoster/FilesMailRu.py105
-rw-r--r--module/plugins/hoster/FileserveCom.py216
-rw-r--r--module/plugins/hoster/FileshareInUa.py19
-rw-r--r--module/plugins/hoster/FilesonicCom.py20
-rw-r--r--module/plugins/hoster/FilezyNet.py19
-rw-r--r--module/plugins/hoster/FiredriveCom.py19
-rw-r--r--module/plugins/hoster/FlyFilesNet.py43
-rw-r--r--module/plugins/hoster/FourSharedCom.py63
-rw-r--r--module/plugins/hoster/FreakshareCom.py182
-rw-r--r--module/plugins/hoster/FreeWayMe.py54
-rw-r--r--module/plugins/hoster/FreevideoCz.py19
-rw-r--r--module/plugins/hoster/FshareVn.py110
-rw-r--r--module/plugins/hoster/Ftp.py77
-rw-r--r--module/plugins/hoster/GamefrontCom.py90
-rw-r--r--module/plugins/hoster/GigapetaCom.py66
-rw-r--r--module/plugins/hoster/GooIm.py38
-rw-r--r--module/plugins/hoster/GoogledriveCom.py66
-rw-r--r--module/plugins/hoster/HellshareCz.py36
-rw-r--r--module/plugins/hoster/HellspyCz.py19
-rw-r--r--module/plugins/hoster/HostujeNet.py49
-rw-r--r--module/plugins/hoster/HotfileCom.py22
-rw-r--r--module/plugins/hoster/HugefilesNet.py25
-rw-r--r--module/plugins/hoster/HundredEightyUploadCom.py21
-rw-r--r--module/plugins/hoster/IFileWs.py19
-rw-r--r--module/plugins/hoster/IcyFilesCom.py19
-rw-r--r--module/plugins/hoster/IfileIt.py19
-rw-r--r--module/plugins/hoster/IfolderRu.py67
-rw-r--r--module/plugins/hoster/JumbofilesCom.py37
-rw-r--r--module/plugins/hoster/JunocloudMe.py24
-rw-r--r--module/plugins/hoster/Keep2ShareCc.py118
-rw-r--r--module/plugins/hoster/KickloadCom.py19
-rw-r--r--module/plugins/hoster/KingfilesNet.py79
-rw-r--r--module/plugins/hoster/LemUploadsCom.py19
-rw-r--r--module/plugins/hoster/LetitbitNet.py136
-rw-r--r--module/plugins/hoster/LinksnappyCom.py58
-rw-r--r--module/plugins/hoster/LoadTo.py67
-rw-r--r--module/plugins/hoster/LolabitsEs.py48
-rw-r--r--module/plugins/hoster/LomafileCom.py20
-rw-r--r--module/plugins/hoster/LuckyShareNet.py75
-rw-r--r--module/plugins/hoster/MediafireCom.py63
-rw-r--r--module/plugins/hoster/MegaCoNz.py217
-rw-r--r--module/plugins/hoster/MegaDebridEu.py60
-rw-r--r--module/plugins/hoster/MegaFilesSe.py19
-rw-r--r--module/plugins/hoster/MegaRapidCz.py65
-rw-r--r--module/plugins/hoster/MegaRapidoNet.py54
-rw-r--r--module/plugins/hoster/MegacrypterCom.py57
-rw-r--r--module/plugins/hoster/MegareleaseOrg.py20
-rw-r--r--module/plugins/hoster/MegasharesCom.py112
-rw-r--r--module/plugins/hoster/MegauploadCom.py19
-rw-r--r--module/plugins/hoster/MegavideoCom.py20
-rw-r--r--module/plugins/hoster/MovReelCom.py21
-rw-r--r--module/plugins/hoster/MultihostersCom.py15
-rw-r--r--module/plugins/hoster/MultishareCz.py54
-rw-r--r--module/plugins/hoster/MyfastfileCom.py39
-rw-r--r--module/plugins/hoster/MystoreTo.py46
-rw-r--r--module/plugins/hoster/MyvideoDe.py49
-rw-r--r--module/plugins/hoster/NahrajCz.py19
-rw-r--r--module/plugins/hoster/NarodRu.py64
-rw-r--r--module/plugins/hoster/NetloadIn.py297
-rw-r--r--module/plugins/hoster/NitroflareCom.py101
-rw-r--r--module/plugins/hoster/NoPremiumPl.py104
-rw-r--r--module/plugins/hoster/NosuploadCom.py42
-rw-r--r--module/plugins/hoster/NovafileCom.py29
-rw-r--r--module/plugins/hoster/NowDownloadSx.py65
-rw-r--r--module/plugins/hoster/NowVideoSx.py45
-rw-r--r--module/plugins/hoster/OboomCom.py145
-rw-r--r--module/plugins/hoster/OneFichierCom.py62
-rw-r--r--module/plugins/hoster/OpenloadIo.py26
-rw-r--r--module/plugins/hoster/OronCom.py20
-rw-r--r--module/plugins/hoster/OverLoadMe.py51
-rw-r--r--module/plugins/hoster/PandaplaNet.py19
-rw-r--r--module/plugins/hoster/PornhostCom.py79
-rw-r--r--module/plugins/hoster/PornhubCom.py89
-rw-r--r--module/plugins/hoster/PotloadCom.py19
-rw-r--r--module/plugins/hoster/PremiumTo.py56
-rw-r--r--module/plugins/hoster/PremiumizeMe.py61
-rw-r--r--module/plugins/hoster/PromptfileCom.py46
-rw-r--r--module/plugins/hoster/PrzeklejPl.py19
-rw-r--r--module/plugins/hoster/PutdriveCom.py15
-rw-r--r--module/plugins/hoster/QuickshareCz.py91
-rw-r--r--module/plugins/hoster/RPNetBiz.py81
-rw-r--r--module/plugins/hoster/RapideoPl.py104
-rw-r--r--module/plugins/hoster/RapidfileshareNet.py25
-rw-r--r--module/plugins/hoster/RapidgatorNet.py162
-rw-r--r--module/plugins/hoster/RapiduNet.py84
-rw-r--r--module/plugins/hoster/RarefileNet.py23
-rw-r--r--module/plugins/hoster/RealdebridCom.py56
-rw-r--r--module/plugins/hoster/RedtubeCom.py62
-rw-r--r--module/plugins/hoster/RehostTo.py29
-rw-r--r--module/plugins/hoster/RemixshareCom.py58
-rw-r--r--module/plugins/hoster/RgHostNet.py28
-rw-r--r--module/plugins/hoster/SafesharingEu.py21
-rw-r--r--module/plugins/hoster/SecureUploadEu.py21
-rw-r--r--module/plugins/hoster/SendspaceCom.py60
-rw-r--r--module/plugins/hoster/Share4WebCom.py22
-rw-r--r--module/plugins/hoster/Share76Com.py19
-rw-r--r--module/plugins/hoster/ShareFilesCo.py19
-rw-r--r--module/plugins/hoster/SharebeesCom.py19
-rw-r--r--module/plugins/hoster/ShareonlineBiz.py184
-rw-r--r--module/plugins/hoster/ShareplaceCom.py88
-rw-r--r--module/plugins/hoster/SharingmatrixCom.py20
-rw-r--r--module/plugins/hoster/ShragleCom.py20
-rw-r--r--module/plugins/hoster/SimplyPremiumCom.py80
-rw-r--r--module/plugins/hoster/SimplydebridCom.py49
-rw-r--r--module/plugins/hoster/SizedriveCom.py41
-rw-r--r--module/plugins/hoster/SmoozedCom.py64
-rw-r--r--module/plugins/hoster/SockshareCom.py21
-rw-r--r--module/plugins/hoster/SolidfilesCom.py33
-rw-r--r--module/plugins/hoster/SoundcloudCom.py56
-rw-r--r--module/plugins/hoster/SpeedLoadOrg.py19
-rw-r--r--module/plugins/hoster/SpeedfileCz.py19
-rw-r--r--module/plugins/hoster/SpeedyshareCom.py44
-rw-r--r--module/plugins/hoster/StorageTo.py19
-rw-r--r--module/plugins/hoster/StreamCz.py71
-rw-r--r--module/plugins/hoster/StreamcloudEu.py31
-rw-r--r--module/plugins/hoster/TurbobitNet.py168
-rw-r--r--module/plugins/hoster/TurbouploadCom.py19
-rw-r--r--module/plugins/hoster/TusfilesNet.py40
-rw-r--r--module/plugins/hoster/TwoSharedCom.py33
-rw-r--r--module/plugins/hoster/UlozTo.py153
-rw-r--r--module/plugins/hoster/UloziskoSk.py71
-rw-r--r--module/plugins/hoster/UnibytesCom.py75
-rw-r--r--module/plugins/hoster/UnrestrictLi.py18
-rw-r--r--module/plugins/hoster/UpleaCom.py66
-rw-r--r--module/plugins/hoster/UploadStationCom.py20
-rw-r--r--module/plugins/hoster/UploadableCh.py77
-rw-r--r--module/plugins/hoster/UploadboxCom.py19
-rw-r--r--module/plugins/hoster/UploadedTo.py124
-rw-r--r--module/plugins/hoster/UploadhereCom.py19
-rw-r--r--module/plugins/hoster/UploadheroCom.py70
-rw-r--r--module/plugins/hoster/UploadingCom.py97
-rw-r--r--module/plugins/hoster/UploadkingCom.py19
-rw-r--r--module/plugins/hoster/UpstoreNet.py73
-rw-r--r--module/plugins/hoster/UptoboxCom.py33
-rw-r--r--module/plugins/hoster/VeehdCom.py81
-rw-r--r--module/plugins/hoster/VeohCom.py54
-rw-r--r--module/plugins/hoster/VidPlayNet.py24
-rw-r--r--module/plugins/hoster/VimeoCom.py75
-rw-r--r--module/plugins/hoster/Vipleech4UCom.py19
-rw-r--r--module/plugins/hoster/WarserverCz.py19
-rw-r--r--module/plugins/hoster/WebshareCz.py64
-rw-r--r--module/plugins/hoster/WrzucTo.py51
-rw-r--r--module/plugins/hoster/WuploadCom.py20
-rw-r--r--module/plugins/hoster/X7To.py19
-rw-r--r--module/plugins/hoster/XFileSharingPro.py59
-rw-r--r--module/plugins/hoster/XHamsterCom.py128
-rw-r--r--module/plugins/hoster/XVideosCom.py27
-rw-r--r--module/plugins/hoster/XdadevelopersCom.py39
-rw-r--r--module/plugins/hoster/Xdcc.py208
-rw-r--r--module/plugins/hoster/YadiSk.py87
-rw-r--r--module/plugins/hoster/YibaishiwuCom.py59
-rw-r--r--module/plugins/hoster/YoupornCom.py60
-rw-r--r--module/plugins/hoster/YourfilesTo.py85
-rw-r--r--module/plugins/hoster/YoutubeCom.py191
-rw-r--r--module/plugins/hoster/ZDF.py59
-rw-r--r--module/plugins/hoster/ZShareNet.py20
-rw-r--r--module/plugins/hoster/ZeveraCom.py34
-rw-r--r--module/plugins/hoster/ZippyshareCom.py94
-rw-r--r--module/plugins/hoster/__init__.py0
-rw-r--r--module/plugins/internal/CaptchaService.py517
-rw-r--r--module/plugins/internal/DeadCrypter.py31
-rw-r--r--module/plugins/internal/DeadHoster.py31
-rw-r--r--module/plugins/internal/Extractor.py150
-rw-r--r--module/plugins/internal/MultiHook.py288
-rw-r--r--module/plugins/internal/MultiHoster.py116
-rw-r--r--module/plugins/internal/SevenZip.py153
-rw-r--r--module/plugins/internal/SimpleCrypter.py167
-rw-r--r--module/plugins/internal/SimpleDereferer.py97
-rw-r--r--module/plugins/internal/SimpleHoster.py762
-rw-r--r--module/plugins/internal/UnRar.py243
-rw-r--r--module/plugins/internal/UnZip.py72
-rw-r--r--module/plugins/internal/XFSAccount.py178
-rw-r--r--module/plugins/internal/XFSCrypter.py45
-rw-r--r--module/plugins/internal/XFSHoster.py323
-rw-r--r--module/plugins/internal/__init__.py0
-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/Processor.py77
-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/__init__.py0
-rw-r--r--module/remote/thriftbackend/pyload.thrift337
-rw-r--r--module/remote/thriftbackend/thriftgen/__init__.py0
-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/__init__.py0
-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/default/css/pathchooser.css68
-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/base.coffee173
-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/servers/nginx_default.conf87
-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/settings_item.html48
-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.py332
-rwxr-xr-xpyLoadCli.py590
-rwxr-xr-xpyLoadCore.py668
-rwxr-xr-xpyLoadGui.py765
-rwxr-xr-xpyload.py7
-rw-r--r--pyload/Api/__init__.py1051
-rw-r--r--pyload/Api/types.py562
-rwxr-xr-xpyload/Core.py677
-rw-r--r--pyload/Database/Backend.py328
-rw-r--r--pyload/Database/File.py985
-rw-r--r--pyload/Database/Storage.py36
-rw-r--r--pyload/Database/User.py93
-rw-r--r--pyload/Database/__init__.py6
-rw-r--r--pyload/Datatype/File.py299
-rw-r--r--pyload/Datatype/Package.py73
-rw-r--r--pyload/Datatype/__init__.py4
-rw-r--r--pyload/Thread/Addon.py66
-rw-r--r--pyload/Thread/Decrypter.py100
-rw-r--r--pyload/Thread/Download.py208
-rw-r--r--pyload/Thread/Info.py216
-rw-r--r--pyload/Thread/Plugin.py129
-rw-r--r--pyload/Thread/Server.py122
-rw-r--r--pyload/Thread/__init__.py7
-rw-r--r--pyload/__init__.py104
-rw-r--r--pyload/config/Parser.py357
-rw-r--r--pyload/config/Setup.py565
-rw-r--r--pyload/config/__init__.py1
-rw-r--r--pyload/config/default.conf76
-rw-r--r--pyload/manager/Account.py196
-rw-r--r--pyload/manager/Addon.py302
-rw-r--r--pyload/manager/Captcha.py158
-rw-r--r--pyload/manager/Event.py132
-rw-r--r--pyload/manager/Plugin.py400
-rw-r--r--pyload/manager/Remote.py85
-rw-r--r--pyload/manager/Scheduler.py140
-rw-r--r--pyload/manager/Thread.py313
-rw-r--r--pyload/manager/__init__.py1
-rw-r--r--pyload/network/Browser.py151
-rw-r--r--pyload/network/Bucket.py50
-rw-r--r--pyload/network/CookieJar.py42
-rw-r--r--pyload/network/HTTPChunk.py318
-rw-r--r--pyload/network/HTTPDownload.py319
-rw-r--r--pyload/network/HTTPRequest.py321
-rw-r--r--pyload/network/JsEngine.py256
-rw-r--r--pyload/network/RequestFactory.py133
-rw-r--r--pyload/network/XDCCRequest.py147
-rw-r--r--pyload/network/__init__.py1
-rw-r--r--pyload/plugin/Account.py309
-rw-r--r--pyload/plugin/Addon.py189
-rw-r--r--pyload/plugin/Captcha.py32
-rw-r--r--pyload/plugin/Container.py64
-rw-r--r--pyload/plugin/Crypter.py107
-rw-r--r--pyload/plugin/Extractor.py154
-rw-r--r--pyload/plugin/Hook.py15
-rw-r--r--pyload/plugin/Hoster.py21
-rw-r--r--pyload/plugin/OCR.py322
-rw-r--r--pyload/plugin/Plugin.py772
-rw-r--r--pyload/plugin/__init__.py1
-rw-r--r--pyload/plugin/account/AlldebridCom.py63
-rw-r--r--pyload/plugin/account/BackinNet.py16
-rw-r--r--pyload/plugin/account/BillionuploadsCom.py16
-rw-r--r--pyload/plugin/account/BitshareCom.py34
-rw-r--r--pyload/plugin/account/CatShareNet.py61
-rw-r--r--pyload/plugin/account/CloudzillaTo.py37
-rw-r--r--pyload/plugin/account/CramitIn.py16
-rw-r--r--pyload/plugin/account/CzshareCom.py54
-rw-r--r--pyload/plugin/account/DebridItaliaCom.py44
-rw-r--r--pyload/plugin/account/DepositfilesCom.py36
-rw-r--r--pyload/plugin/account/DropboxCom.py34
-rw-r--r--pyload/plugin/account/EasybytezCom.py17
-rw-r--r--pyload/plugin/account/EuroshareEu.py41
-rw-r--r--pyload/plugin/account/ExashareCom.py16
-rw-r--r--pyload/plugin/account/FastixRu.py42
-rw-r--r--pyload/plugin/account/FastshareCz.py51
-rw-r--r--pyload/plugin/account/File4SafeCom.py18
-rw-r--r--pyload/plugin/account/FileParadoxIn.py16
-rw-r--r--pyload/plugin/account/FilecloudIo.py59
-rw-r--r--pyload/plugin/account/FilefactoryCom.py48
-rw-r--r--pyload/plugin/account/FilejungleCom.py50
-rw-r--r--pyload/plugin/account/FileomCom.py16
-rw-r--r--pyload/plugin/account/FilerNet.py59
-rw-r--r--pyload/plugin/account/FilerioCom.py16
-rw-r--r--pyload/plugin/account/FilesMailRu.py31
-rw-r--r--pyload/plugin/account/FileserveCom.py44
-rw-r--r--pyload/plugin/account/FourSharedCom.py35
-rw-r--r--pyload/plugin/account/FreakshareCom.py51
-rw-r--r--pyload/plugin/account/FreeWayMe.py52
-rw-r--r--pyload/plugin/account/FshareVn.py62
-rw-r--r--pyload/plugin/account/Ftp.py17
-rw-r--r--pyload/plugin/account/HellshareCz.py79
-rw-r--r--pyload/plugin/account/Http.py17
-rw-r--r--pyload/plugin/account/HugefilesNet.py16
-rw-r--r--pyload/plugin/account/HundredEightyUploadCom.py16
-rw-r--r--pyload/plugin/account/JunkyvideoCom.py16
-rw-r--r--pyload/plugin/account/JunocloudMe.py16
-rw-r--r--pyload/plugin/account/Keep2ShareCc.py73
-rw-r--r--pyload/plugin/account/LetitbitNet.py34
-rw-r--r--pyload/plugin/account/LinestorageCom.py17
-rw-r--r--pyload/plugin/account/LinksnappyCom.py57
-rw-r--r--pyload/plugin/account/MegaDebridEu.py39
-rw-r--r--pyload/plugin/account/MegaRapidCz.py60
-rw-r--r--pyload/plugin/account/MegaRapidoNet.py57
-rw-r--r--pyload/plugin/account/MegasharesCom.py48
-rw-r--r--pyload/plugin/account/MovReelCom.py19
-rw-r--r--pyload/plugin/account/MultihostersCom.py16
-rw-r--r--pyload/plugin/account/MultishareCz.py44
-rw-r--r--pyload/plugin/account/MyfastfileCom.py37
-rw-r--r--pyload/plugin/account/NetloadIn.py63
-rw-r--r--pyload/plugin/account/NoPremiumPl.py85
-rw-r--r--pyload/plugin/account/NosuploadCom.py16
-rw-r--r--pyload/plugin/account/NovafileCom.py16
-rw-r--r--pyload/plugin/account/NowVideoSx.py56
-rw-r--r--pyload/plugin/account/OboomCom.py79
-rw-r--r--pyload/plugin/account/OneFichierCom.py58
-rw-r--r--pyload/plugin/account/OverLoadMe.py43
-rw-r--r--pyload/plugin/account/PremiumTo.py37
-rw-r--r--pyload/plugin/account/PremiumizeMe.py49
-rw-r--r--pyload/plugin/account/PutdriveCom.py16
-rw-r--r--pyload/plugin/account/QuickshareCz.py43
-rw-r--r--pyload/plugin/account/RPNetBiz.py51
-rw-r--r--pyload/plugin/account/RapideoPl.py84
-rw-r--r--pyload/plugin/account/RapidfileshareNet.py18
-rw-r--r--pyload/plugin/account/RapidgatorNet.py72
-rw-r--r--pyload/plugin/account/RapiduNet.py65
-rw-r--r--pyload/plugin/account/RarefileNet.py16
-rw-r--r--pyload/plugin/account/RealdebridCom.py40
-rw-r--r--pyload/plugin/account/RehostTo.py54
-rw-r--r--pyload/plugin/account/RyushareCom.py16
-rw-r--r--pyload/plugin/account/SafesharingEu.py16
-rw-r--r--pyload/plugin/account/SecureUploadEu.py16
-rw-r--r--pyload/plugin/account/SendmywayCom.py16
-rw-r--r--pyload/plugin/account/ShareonlineBiz.py65
-rw-r--r--pyload/plugin/account/SimplyPremiumCom.py48
-rw-r--r--pyload/plugin/account/SimplydebridCom.py35
-rw-r--r--pyload/plugin/account/SmoozedCom.py80
-rw-r--r--pyload/plugin/account/StahnuTo.py35
-rw-r--r--pyload/plugin/account/StreamcloudEu.py16
-rw-r--r--pyload/plugin/account/TurbobitNet.py43
-rw-r--r--pyload/plugin/account/TusfilesNet.py19
-rw-r--r--pyload/plugin/account/UlozTo.py49
-rw-r--r--pyload/plugin/account/UploadableCh.py34
-rw-r--r--pyload/plugin/account/UploadcCom.py16
-rw-r--r--pyload/plugin/account/UploadedTo.py67
-rw-r--r--pyload/plugin/account/UploadheroCom.py42
-rw-r--r--pyload/plugin/account/UploadingCom.py65
-rw-r--r--pyload/plugin/account/UptoboxCom.py18
-rw-r--r--pyload/plugin/account/VidPlayNet.py16
-rw-r--r--pyload/plugin/account/WebshareCz.py67
-rw-r--r--pyload/plugin/account/XFileSharingPro.py34
-rw-r--r--pyload/plugin/account/YibaishiwuCom.py40
-rw-r--r--pyload/plugin/account/ZeveraCom.py73
-rw-r--r--pyload/plugin/account/__init__.py1
-rw-r--r--pyload/plugin/addon/AndroidPhoneNotify.py108
-rw-r--r--pyload/plugin/addon/AntiVirus.py115
-rw-r--r--pyload/plugin/addon/Checksum.py195
-rw-r--r--pyload/plugin/addon/ClickNLoad.py106
-rw-r--r--pyload/plugin/addon/DeleteFinished.py87
-rw-r--r--pyload/plugin/addon/DownloadScheduler.py72
-rw-r--r--pyload/plugin/addon/ExternalScripts.py215
-rw-r--r--pyload/plugin/addon/ExtractArchive.py571
-rw-r--r--pyload/plugin/addon/HotFolder.py73
-rw-r--r--pyload/plugin/addon/IRCInterface.py431
-rw-r--r--pyload/plugin/addon/JustPremium.py51
-rw-r--r--pyload/plugin/addon/MergeFiles.py80
-rw-r--r--pyload/plugin/addon/MultiHome.py84
-rw-r--r--pyload/plugin/addon/RestartFailed.py43
-rw-r--r--pyload/plugin/addon/SkipRev.py104
-rw-r--r--pyload/plugin/addon/UnSkipOnFail.py90
-rw-r--r--pyload/plugin/addon/UpdateManager.py324
-rw-r--r--pyload/plugin/addon/UserAgentSwitcher.py40
-rw-r--r--pyload/plugin/addon/WindowsPhoneNotify.py124
-rw-r--r--pyload/plugin/addon/XMPPInterface.py251
-rw-r--r--pyload/plugin/addon/__init__.py1
-rw-r--r--pyload/plugin/captcha/AdYouLike.py108
-rw-r--r--pyload/plugin/captcha/AdsCaptcha.py79
-rw-r--r--pyload/plugin/captcha/ReCaptcha.py198
-rw-r--r--pyload/plugin/captcha/SolveMedia.py114
-rw-r--r--pyload/plugin/captcha/__init__.py1
-rw-r--r--pyload/plugin/container/CCF.py47
-rw-r--r--pyload/plugin/container/DLC.py72
-rw-r--r--pyload/plugin/container/RSDF.py61
-rw-r--r--pyload/plugin/container/TXT.py69
-rw-r--r--pyload/plugin/container/__init__.py1
-rw-r--r--pyload/plugin/crypter/BitshareCom.py22
-rw-r--r--pyload/plugin/crypter/C1NeonCom.py16
-rw-r--r--pyload/plugin/crypter/ChipDe.py29
-rw-r--r--pyload/plugin/crypter/CloudzillaTo.py34
-rw-r--r--pyload/plugin/crypter/CrockoCom.py21
-rw-r--r--pyload/plugin/crypter/CryptItCom.py16
-rw-r--r--pyload/plugin/crypter/CzshareCom.py32
-rw-r--r--pyload/plugin/crypter/DailymotionComFolder.py105
-rw-r--r--pyload/plugin/crypter/DataHu.py39
-rw-r--r--pyload/plugin/crypter/DdlstorageCom.py17
-rw-r--r--pyload/plugin/crypter/DepositfilesCom.py21
-rw-r--r--pyload/plugin/crypter/Dereferer.py17
-rw-r--r--pyload/plugin/crypter/DevhostSt.py61
-rw-r--r--pyload/plugin/crypter/DlProtectCom.py65
-rw-r--r--pyload/plugin/crypter/DontKnowMe.py17
-rw-r--r--pyload/plugin/crypter/DuckCryptInfo.py59
-rw-r--r--pyload/plugin/crypter/DuploadOrg.py16
-rw-r--r--pyload/plugin/crypter/EasybytezCom.py20
-rw-r--r--pyload/plugin/crypter/EmbeduploadCom.py60
-rw-r--r--pyload/plugin/crypter/FilebeerInfo.py16
-rw-r--r--pyload/plugin/crypter/FilecloudIo.py22
-rw-r--r--pyload/plugin/crypter/FilecryptCc.py179
-rw-r--r--pyload/plugin/crypter/FilefactoryCom.py29
-rw-r--r--pyload/plugin/crypter/FilerNet.py25
-rw-r--r--pyload/plugin/crypter/FileserveCom.py38
-rw-r--r--pyload/plugin/crypter/FilesonicCom.py16
-rw-r--r--pyload/plugin/crypter/FilestubeCom.py22
-rw-r--r--pyload/plugin/crypter/FiletramCom.py23
-rw-r--r--pyload/plugin/crypter/FiredriveCom.py16
-rw-r--r--pyload/plugin/crypter/FourChanOrg.py27
-rw-r--r--pyload/plugin/crypter/FreakhareCom.py39
-rw-r--r--pyload/plugin/crypter/FreetexthostCom.py28
-rw-r--r--pyload/plugin/crypter/FshareVn.py21
-rw-r--r--pyload/plugin/crypter/Go4UpCom.py48
-rw-r--r--pyload/plugin/crypter/GooGl.py32
-rw-r--r--pyload/plugin/crypter/HoerbuchIn.py62
-rw-r--r--pyload/plugin/crypter/HotfileCom.py16
-rw-r--r--pyload/plugin/crypter/ILoadTo.py16
-rw-r--r--pyload/plugin/crypter/ImgurComAlbum.py28
-rw-r--r--pyload/plugin/crypter/LetitbitNet.py33
-rw-r--r--pyload/plugin/crypter/LinkCryptWs.py322
-rw-r--r--pyload/plugin/crypter/LinkSaveIn.py22
-rw-r--r--pyload/plugin/crypter/LinkdecrypterCom.py69
-rw-r--r--pyload/plugin/crypter/LixIn.py62
-rw-r--r--pyload/plugin/crypter/LofCc.py16
-rw-r--r--pyload/plugin/crypter/MBLinkInfo.py17
-rw-r--r--pyload/plugin/crypter/MediafireCom.py58
-rw-r--r--pyload/plugin/crypter/MegaCoNz.py29
-rw-r--r--pyload/plugin/crypter/MegaRapidCz.py21
-rw-r--r--pyload/plugin/crypter/MegauploadCom.py16
-rw-r--r--pyload/plugin/crypter/Movie2KTo.py16
-rw-r--r--pyload/plugin/crypter/MultiUpOrg.py39
-rw-r--r--pyload/plugin/crypter/MultiloadCz.py42
-rw-r--r--pyload/plugin/crypter/MultiuploadCom.py15
-rw-r--r--pyload/plugin/crypter/NCryptIn.py310
-rw-r--r--pyload/plugin/crypter/NetfolderIn.py71
-rw-r--r--pyload/plugin/crypter/NosvideoCom.py22
-rw-r--r--pyload/plugin/crypter/OneKhDe.py41
-rw-r--r--pyload/plugin/crypter/OronCom.py16
-rw-r--r--pyload/plugin/crypter/PastebinCom.py22
-rw-r--r--pyload/plugin/crypter/QuickshareCz.py31
-rw-r--r--pyload/plugin/crypter/RSLayerCom.py16
-rw-r--r--pyload/plugin/crypter/RelinkUs.py294
-rw-r--r--pyload/plugin/crypter/SafelinkingNet.py81
-rw-r--r--pyload/plugin/crypter/SecuredIn.py16
-rw-r--r--pyload/plugin/crypter/SexuriaCom.py94
-rw-r--r--pyload/plugin/crypter/ShareLinksBiz.py280
-rw-r--r--pyload/plugin/crypter/SharingmatrixCom.py16
-rw-r--r--pyload/plugin/crypter/SpeedLoadOrg.py16
-rw-r--r--pyload/plugin/crypter/StealthTo.py16
-rw-r--r--pyload/plugin/crypter/TnyCz.py28
-rw-r--r--pyload/plugin/crypter/TrailerzoneInfo.py16
-rw-r--r--pyload/plugin/crypter/TurbobitNet.py45
-rw-r--r--pyload/plugin/crypter/TusfilesNet.py43
-rw-r--r--pyload/plugin/crypter/UlozTo.py46
-rw-r--r--pyload/plugin/crypter/UploadableCh.py25
-rw-r--r--pyload/plugin/crypter/UploadedTo.py34
-rw-r--r--pyload/plugin/crypter/WiiReloadedOrg.py16
-rw-r--r--pyload/plugin/crypter/WuploadCom.py16
-rw-r--r--pyload/plugin/crypter/XFileSharingPro.py49
-rw-r--r--pyload/plugin/crypter/XupPl.py25
-rw-r--r--pyload/plugin/crypter/YoutubeComFolder.py147
-rw-r--r--pyload/plugin/crypter/__init__.py1
-rw-r--r--pyload/plugin/extractor/SevenZip.py154
-rw-r--r--pyload/plugin/extractor/UnRar.py243
-rw-r--r--pyload/plugin/extractor/UnZip.py74
-rw-r--r--pyload/plugin/extractor/__init__.py1
-rw-r--r--pyload/plugin/hook/AlldebridCom.py27
-rw-r--r--pyload/plugin/hook/BypassCaptcha.py135
-rw-r--r--pyload/plugin/hook/Captcha9Kw.py250
-rw-r--r--pyload/plugin/hook/CaptchaBrotherhood.py171
-rw-r--r--pyload/plugin/hook/DeathByCaptcha.py219
-rw-r--r--pyload/plugin/hook/DebridItaliaCom.py26
-rw-r--r--pyload/plugin/hook/EasybytezCom.py30
-rw-r--r--pyload/plugin/hook/ExpertDecoders.py102
-rw-r--r--pyload/plugin/hook/FastixRu.py29
-rw-r--r--pyload/plugin/hook/FreeWayMe.py32
-rw-r--r--pyload/plugin/hook/ImageTyperz.py153
-rw-r--r--pyload/plugin/hook/LinkdecrypterCom.py32
-rw-r--r--pyload/plugin/hook/LinksnappyCom.py27
-rw-r--r--pyload/plugin/hook/MegaDebridEu.py33
-rw-r--r--pyload/plugin/hook/MegaRapidoNet.py81
-rw-r--r--pyload/plugin/hook/MultihostersCom.py18
-rw-r--r--pyload/plugin/hook/MultishareCz.py29
-rw-r--r--pyload/plugin/hook/MyfastfileCom.py28
-rw-r--r--pyload/plugin/hook/NoPremiumPl.py29
-rw-r--r--pyload/plugin/hook/OverLoadMe.py29
-rw-r--r--pyload/plugin/hook/PremiumTo.py27
-rw-r--r--pyload/plugin/hook/PremiumizeMe.py38
-rw-r--r--pyload/plugin/hook/PutdriveCom.py18
-rw-r--r--pyload/plugin/hook/RPNetBiz.py36
-rw-r--r--pyload/plugin/hook/RapideoPl.py29
-rw-r--r--pyload/plugin/hook/RealdebridCom.py27
-rw-r--r--pyload/plugin/hook/RehostTo.py27
-rw-r--r--pyload/plugin/hook/SimplyPremiumCom.py29
-rw-r--r--pyload/plugin/hook/SimplydebridCom.py24
-rw-r--r--pyload/plugin/hook/SmoozedCom.py24
-rw-r--r--pyload/plugin/hook/XFileSharingPro.py110
-rw-r--r--pyload/plugin/hook/ZeveraCom.py25
-rw-r--r--pyload/plugin/hook/__init__.py1
-rw-r--r--pyload/plugin/hoster/AlldebridCom.py48
-rw-r--r--pyload/plugin/hoster/AndroidfilehostCom.py61
-rw-r--r--pyload/plugin/hoster/BasketbuildCom.py59
-rw-r--r--pyload/plugin/hoster/BayfilesCom.py16
-rw-r--r--pyload/plugin/hoster/BezvadataCz.py92
-rw-r--r--pyload/plugin/hoster/BillionuploadsCom.py15
-rw-r--r--pyload/plugin/hoster/BitshareCom.py156
-rw-r--r--pyload/plugin/hoster/BoltsharingCom.py16
-rw-r--r--pyload/plugin/hoster/CatShareNet.py59
-rw-r--r--pyload/plugin/hoster/CloudzerNet.py18
-rw-r--r--pyload/plugin/hoster/CloudzillaTo.py59
-rw-r--r--pyload/plugin/hoster/CramitIn.py20
-rw-r--r--pyload/plugin/hoster/CrockoCom.py64
-rw-r--r--pyload/plugin/hoster/CyberlockerCh.py16
-rw-r--r--pyload/plugin/hoster/CzshareCom.py159
-rw-r--r--pyload/plugin/hoster/DailymotionCom.py125
-rw-r--r--pyload/plugin/hoster/DataHu.py30
-rw-r--r--pyload/plugin/hoster/DataportCz.py55
-rw-r--r--pyload/plugin/hoster/DateiTo.py80
-rw-r--r--pyload/plugin/hoster/DdlstorageCom.py17
-rw-r--r--pyload/plugin/hoster/DebridItaliaCom.py41
-rw-r--r--pyload/plugin/hoster/DepositfilesCom.py89
-rw-r--r--pyload/plugin/hoster/DevhostSt.py32
-rw-r--r--pyload/plugin/hoster/DlFreeFr.py138
-rw-r--r--pyload/plugin/hoster/DodanePl.py16
-rw-r--r--pyload/plugin/hoster/DuploadOrg.py16
-rw-r--r--pyload/plugin/hoster/EasybytezCom.py21
-rw-r--r--pyload/plugin/hoster/EdiskCz.py54
-rw-r--r--pyload/plugin/hoster/EgoFilesCom.py16
-rw-r--r--pyload/plugin/hoster/EnteruploadCom.py16
-rw-r--r--pyload/plugin/hoster/EpicShareNet.py16
-rw-r--r--pyload/plugin/hoster/EuroshareEu.py65
-rw-r--r--pyload/plugin/hoster/ExashareCom.py35
-rw-r--r--pyload/plugin/hoster/ExtabitCom.py75
-rw-r--r--pyload/plugin/hoster/FastixRu.py38
-rw-r--r--pyload/plugin/hoster/FastshareCz.py77
-rw-r--r--pyload/plugin/hoster/FileApeCom.py16
-rw-r--r--pyload/plugin/hoster/FileSharkPl.py114
-rw-r--r--pyload/plugin/hoster/FileStoreTo.py35
-rw-r--r--pyload/plugin/hoster/FilebeerInfo.py16
-rw-r--r--pyload/plugin/hoster/FilecloudIo.py123
-rw-r--r--pyload/plugin/hoster/FilefactoryCom.py85
-rw-r--r--pyload/plugin/hoster/FilejungleCom.py29
-rw-r--r--pyload/plugin/hoster/FileomCom.py30
-rw-r--r--pyload/plugin/hoster/FilepostCom.py121
-rw-r--r--pyload/plugin/hoster/FilepupNet.py45
-rw-r--r--pyload/plugin/hoster/FilerNet.py57
-rw-r--r--pyload/plugin/hoster/FilerioCom.py20
-rw-r--r--pyload/plugin/hoster/FilesMailRu.py105
-rw-r--r--pyload/plugin/hoster/FileserveCom.py216
-rw-r--r--pyload/plugin/hoster/FileshareInUa.py16
-rw-r--r--pyload/plugin/hoster/FilesonicCom.py17
-rw-r--r--pyload/plugin/hoster/FilezyNet.py16
-rw-r--r--pyload/plugin/hoster/FiredriveCom.py16
-rw-r--r--pyload/plugin/hoster/FlyFilesNet.py43
-rw-r--r--pyload/plugin/hoster/FourSharedCom.py60
-rw-r--r--pyload/plugin/hoster/FreakshareCom.py182
-rw-r--r--pyload/plugin/hoster/FreeWayMe.py51
-rw-r--r--pyload/plugin/hoster/FreevideoCz.py16
-rw-r--r--pyload/plugin/hoster/FshareVn.py110
-rw-r--r--pyload/plugin/hoster/Ftp.py75
-rw-r--r--pyload/plugin/hoster/GamefrontCom.py90
-rw-r--r--pyload/plugin/hoster/GigapetaCom.py59
-rw-r--r--pyload/plugin/hoster/GooIm.py33
-rw-r--r--pyload/plugin/hoster/GoogledriveCom.py63
-rw-r--r--pyload/plugin/hoster/HellshareCz.py31
-rw-r--r--pyload/plugin/hoster/HellspyCz.py16
-rw-r--r--pyload/plugin/hoster/HostujeNet.py46
-rw-r--r--pyload/plugin/hoster/HotfileCom.py19
-rw-r--r--pyload/plugin/hoster/HugefilesNet.py22
-rw-r--r--pyload/plugin/hoster/HundredEightyUploadCom.py18
-rw-r--r--pyload/plugin/hoster/IFileWs.py16
-rw-r--r--pyload/plugin/hoster/IcyFilesCom.py16
-rw-r--r--pyload/plugin/hoster/IfileIt.py16
-rw-r--r--pyload/plugin/hoster/IfolderRu.py63
-rw-r--r--pyload/plugin/hoster/JumbofilesCom.py34
-rw-r--r--pyload/plugin/hoster/JunocloudMe.py21
-rw-r--r--pyload/plugin/hoster/Keep2ShareCc.py115
-rw-r--r--pyload/plugin/hoster/KickloadCom.py16
-rw-r--r--pyload/plugin/hoster/KingfilesNet.py76
-rw-r--r--pyload/plugin/hoster/LemUploadsCom.py16
-rw-r--r--pyload/plugin/hoster/LetitbitNet.py136
-rw-r--r--pyload/plugin/hoster/LinksnappyCom.py55
-rw-r--r--pyload/plugin/hoster/LoadTo.py64
-rw-r--r--pyload/plugin/hoster/LolabitsEs.py45
-rw-r--r--pyload/plugin/hoster/LomafileCom.py17
-rw-r--r--pyload/plugin/hoster/LuckyShareNet.py73
-rw-r--r--pyload/plugin/hoster/MediafireCom.py60
-rw-r--r--pyload/plugin/hoster/MegaCoNz.py215
-rw-r--r--pyload/plugin/hoster/MegaDebridEu.py54
-rw-r--r--pyload/plugin/hoster/MegaFilesSe.py16
-rw-r--r--pyload/plugin/hoster/MegaRapidCz.py65
-rw-r--r--pyload/plugin/hoster/MegaRapidoNet.py54
-rw-r--r--pyload/plugin/hoster/MegacrypterCom.py57
-rw-r--r--pyload/plugin/hoster/MegareleaseOrg.py17
-rw-r--r--pyload/plugin/hoster/MegasharesCom.py109
-rw-r--r--pyload/plugin/hoster/MegauploadCom.py16
-rw-r--r--pyload/plugin/hoster/MegavideoCom.py17
-rw-r--r--pyload/plugin/hoster/MovReelCom.py18
-rw-r--r--pyload/plugin/hoster/MultihostersCom.py15
-rw-r--r--pyload/plugin/hoster/MultishareCz.py50
-rw-r--r--pyload/plugin/hoster/MyfastfileCom.py34
-rw-r--r--pyload/plugin/hoster/MystoreTo.py43
-rw-r--r--pyload/plugin/hoster/MyvideoDe.py49
-rw-r--r--pyload/plugin/hoster/NahrajCz.py16
-rw-r--r--pyload/plugin/hoster/NarodRu.py61
-rw-r--r--pyload/plugin/hoster/NetloadIn.py297
-rw-r--r--pyload/plugin/hoster/NitroflareCom.py101
-rw-r--r--pyload/plugin/hoster/NoPremiumPl.py104
-rw-r--r--pyload/plugin/hoster/NosuploadCom.py39
-rw-r--r--pyload/plugin/hoster/NovafileCom.py26
-rw-r--r--pyload/plugin/hoster/NowDownloadSx.py62
-rw-r--r--pyload/plugin/hoster/NowVideoSx.py42
-rw-r--r--pyload/plugin/hoster/OboomCom.py145
-rw-r--r--pyload/plugin/hoster/OneFichierCom.py57
-rw-r--r--pyload/plugin/hoster/OpenloadIo.py27
-rw-r--r--pyload/plugin/hoster/OronCom.py17
-rw-r--r--pyload/plugin/hoster/OverLoadMe.py45
-rw-r--r--pyload/plugin/hoster/PandaplaNet.py16
-rw-r--r--pyload/plugin/hoster/PornhostCom.py81
-rw-r--r--pyload/plugin/hoster/PornhubCom.py89
-rw-r--r--pyload/plugin/hoster/PotloadCom.py16
-rw-r--r--pyload/plugin/hoster/PremiumTo.py53
-rw-r--r--pyload/plugin/hoster/PremiumizeMe.py58
-rw-r--r--pyload/plugin/hoster/PromptfileCom.py43
-rw-r--r--pyload/plugin/hoster/PrzeklejPl.py16
-rw-r--r--pyload/plugin/hoster/PutdriveCom.py15
-rw-r--r--pyload/plugin/hoster/QuickshareCz.py84
-rw-r--r--pyload/plugin/hoster/RPNetBiz.py76
-rw-r--r--pyload/plugin/hoster/RapideoPl.py104
-rw-r--r--pyload/plugin/hoster/RapidfileshareNet.py22
-rw-r--r--pyload/plugin/hoster/RapidgatorNet.py161
-rw-r--r--pyload/plugin/hoster/RapiduNet.py80
-rw-r--r--pyload/plugin/hoster/RarefileNet.py18
-rw-r--r--pyload/plugin/hoster/RealdebridCom.py51
-rw-r--r--pyload/plugin/hoster/RedtubeCom.py62
-rw-r--r--pyload/plugin/hoster/RehostTo.py24
-rw-r--r--pyload/plugin/hoster/RemixshareCom.py55
-rw-r--r--pyload/plugin/hoster/RgHostNet.py23
-rw-r--r--pyload/plugin/hoster/SafesharingEu.py18
-rw-r--r--pyload/plugin/hoster/SecureUploadEu.py18
-rw-r--r--pyload/plugin/hoster/SendspaceCom.py57
-rw-r--r--pyload/plugin/hoster/Share4WebCom.py18
-rw-r--r--pyload/plugin/hoster/Share76Com.py16
-rw-r--r--pyload/plugin/hoster/ShareFilesCo.py16
-rw-r--r--pyload/plugin/hoster/SharebeesCom.py16
-rw-r--r--pyload/plugin/hoster/ShareonlineBiz.py181
-rw-r--r--pyload/plugin/hoster/ShareplaceCom.py88
-rw-r--r--pyload/plugin/hoster/SharingmatrixCom.py17
-rw-r--r--pyload/plugin/hoster/ShragleCom.py17
-rw-r--r--pyload/plugin/hoster/SimplyPremiumCom.py77
-rw-r--r--pyload/plugin/hoster/SimplydebridCom.py44
-rw-r--r--pyload/plugin/hoster/SizedriveCom.py38
-rw-r--r--pyload/plugin/hoster/SmoozedCom.py64
-rw-r--r--pyload/plugin/hoster/SockshareCom.py18
-rw-r--r--pyload/plugin/hoster/SolidfilesCom.py30
-rw-r--r--pyload/plugin/hoster/SoundcloudCom.py53
-rw-r--r--pyload/plugin/hoster/SpeedLoadOrg.py16
-rw-r--r--pyload/plugin/hoster/SpeedfileCz.py16
-rw-r--r--pyload/plugin/hoster/SpeedyshareCom.py40
-rw-r--r--pyload/plugin/hoster/StorageTo.py16
-rw-r--r--pyload/plugin/hoster/StreamCz.py71
-rw-r--r--pyload/plugin/hoster/StreamcloudEu.py26
-rw-r--r--pyload/plugin/hoster/TurbobitNet.py165
-rw-r--r--pyload/plugin/hoster/TurbouploadCom.py16
-rw-r--r--pyload/plugin/hoster/TusfilesNet.py37
-rw-r--r--pyload/plugin/hoster/TwoSharedCom.py28
-rw-r--r--pyload/plugin/hoster/UlozTo.py150
-rw-r--r--pyload/plugin/hoster/UloziskoSk.py68
-rw-r--r--pyload/plugin/hoster/UnibytesCom.py67
-rw-r--r--pyload/plugin/hoster/UnrestrictLi.py15
-rw-r--r--pyload/plugin/hoster/UpleaCom.py63
-rw-r--r--pyload/plugin/hoster/UploadStationCom.py17
-rw-r--r--pyload/plugin/hoster/UploadableCh.py74
-rw-r--r--pyload/plugin/hoster/UploadboxCom.py16
-rw-r--r--pyload/plugin/hoster/UploadedTo.py121
-rw-r--r--pyload/plugin/hoster/UploadhereCom.py16
-rw-r--r--pyload/plugin/hoster/UploadheroCom.py67
-rw-r--r--pyload/plugin/hoster/UploadingCom.py94
-rw-r--r--pyload/plugin/hoster/UploadkingCom.py16
-rw-r--r--pyload/plugin/hoster/UpstoreNet.py70
-rw-r--r--pyload/plugin/hoster/UptoboxCom.py30
-rw-r--r--pyload/plugin/hoster/VeehdCom.py81
-rw-r--r--pyload/plugin/hoster/VeohCom.py51
-rw-r--r--pyload/plugin/hoster/VidPlayNet.py21
-rw-r--r--pyload/plugin/hoster/VimeoCom.py72
-rw-r--r--pyload/plugin/hoster/Vipleech4UCom.py16
-rw-r--r--pyload/plugin/hoster/WarserverCz.py16
-rw-r--r--pyload/plugin/hoster/WebshareCz.py61
-rw-r--r--pyload/plugin/hoster/WrzucTo.py48
-rw-r--r--pyload/plugin/hoster/WuploadCom.py17
-rw-r--r--pyload/plugin/hoster/X7To.py16
-rw-r--r--pyload/plugin/hoster/XFileSharingPro.py56
-rw-r--r--pyload/plugin/hoster/XHamsterCom.py128
-rw-r--r--pyload/plugin/hoster/XVideosCom.py27
-rw-r--r--pyload/plugin/hoster/XdadevelopersCom.py35
-rw-r--r--pyload/plugin/hoster/Xdcc.py207
-rw-r--r--pyload/plugin/hoster/YadiSk.py84
-rw-r--r--pyload/plugin/hoster/YibaishiwuCom.py56
-rw-r--r--pyload/plugin/hoster/YoupornCom.py60
-rw-r--r--pyload/plugin/hoster/YourfilesTo.py86
-rw-r--r--pyload/plugin/hoster/YoutubeCom.py192
-rw-r--r--pyload/plugin/hoster/ZDF.py58
-rw-r--r--pyload/plugin/hoster/ZShareNet.py17
-rw-r--r--pyload/plugin/hoster/ZeveraCom.py28
-rw-r--r--pyload/plugin/hoster/ZippyshareCom.py92
-rw-r--r--pyload/plugin/hoster/__init__.py1
-rw-r--r--pyload/plugin/internal/BasePlugin.py98
-rw-r--r--pyload/plugin/internal/DeadCrypter.py27
-rw-r--r--pyload/plugin/internal/DeadHoster.py27
-rw-r--r--pyload/plugin/internal/MultiHook.py276
-rw-r--r--pyload/plugin/internal/MultiHoster.py116
-rw-r--r--pyload/plugin/internal/SimpleCrypter.py153
-rw-r--r--pyload/plugin/internal/SimpleDereferer.py94
-rw-r--r--pyload/plugin/internal/SimpleHoster.py758
-rw-r--r--pyload/plugin/internal/XFSAccount.py173
-rw-r--r--pyload/plugin/internal/XFSCrypter.py45
-rw-r--r--pyload/plugin/internal/XFSHoster.py317
-rw-r--r--pyload/plugin/internal/__init__.py1
-rw-r--r--pyload/plugin/ocr/GigasizeCom.py24
-rw-r--r--pyload/plugin/ocr/LinksaveIn.py157
-rw-r--r--pyload/plugin/ocr/NetloadIn.py29
-rw-r--r--pyload/plugin/ocr/ShareonlineBiz.py39
-rw-r--r--pyload/plugin/ocr/__init__.py1
-rw-r--r--pyload/remote/ClickNLoadBackend.py171
-rw-r--r--pyload/remote/SocketBackend.py26
-rw-r--r--pyload/remote/ThriftBackend.py44
-rw-r--r--pyload/remote/__init__.py3
-rw-r--r--pyload/remote/socketbackend/__init__.py1
-rw-r--r--pyload/remote/socketbackend/create_ttypes.py87
-rw-r--r--pyload/remote/thriftbackend/Processor.py81
-rw-r--r--pyload/remote/thriftbackend/Protocol.py33
-rw-r--r--pyload/remote/thriftbackend/Socket.py144
-rw-r--r--pyload/remote/thriftbackend/ThriftClient.py91
-rw-r--r--pyload/remote/thriftbackend/ThriftTest.py86
-rw-r--r--pyload/remote/thriftbackend/Transport.py44
-rw-r--r--pyload/remote/thriftbackend/__init__.py1
-rw-r--r--pyload/remote/thriftbackend/pyload.thrift337
-rw-r--r--pyload/remote/thriftbackend/thriftgen/__init__.py1
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote571
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py5976
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/__init__.py3
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/constants.py11
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py860
-rw-r--r--pyload/utils/__init__.py263
-rw-r--r--pyload/utils/filters.py68
-rw-r--r--pyload/utils/middlewares.py144
-rw-r--r--pyload/utils/packagetools.py154
-rw-r--r--pyload/utils/printer.py17
-rw-r--r--pyload/utils/pylgettext.py57
-rw-r--r--pyload/webui/__init__.py124
-rw-r--r--pyload/webui/app/__init__.py3
-rw-r--r--pyload/webui/app/api.py99
-rw-r--r--pyload/webui/app/cnl.py172
-rw-r--r--pyload/webui/app/json.py314
-rw-r--r--pyload/webui/app/pyload.py526
-rw-r--r--pyload/webui/app/utils.py124
-rw-r--r--pyload/webui/servers/lighttpd_default.conf154
-rw-r--r--pyload/webui/servers/nginx_default.conf87
-rw-r--r--pyload/webui/themes/Dark/css/MooDialog.css94
-rw-r--r--pyload/webui/themes/Dark/css/base.css962
-rw-r--r--pyload/webui/themes/Dark/css/log.css75
-rw-r--r--pyload/webui/themes/Dark/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/Dark/css/window.css92
-rw-r--r--pyload/webui/themes/Dark/img/add_folder.png (renamed from module/web/media/default/img/add_folder.png)bin571 -> 571 bytes
-rw-r--r--pyload/webui/themes/Dark/img/ajax-loader.gif (renamed from module/web/media/default/img/ajax-loader.gif)bin404 -> 404 bytes
-rw-r--r--pyload/webui/themes/Dark/img/arrow_refresh.png (renamed from module/web/media/default/img/arrow_refresh.png)bin685 -> 685 bytes
-rw-r--r--pyload/webui/themes/Dark/img/arrow_right.png (renamed from module/web/media/default/img/arrow_right.png)bin349 -> 349 bytes
-rw-r--r--pyload/webui/themes/Dark/img/big_button.gif (renamed from module/web/media/default/img/big_button.gif)bin1905 -> 1905 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/body.png (renamed from module/web/media/default/img/body.png)bin402 -> 402 bytes
-rw-r--r--pyload/webui/themes/Dark/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/Dark/img/closebtn.gif (renamed from module/web/media/default/img/closebtn.gif)bin254 -> 254 bytes
-rw-r--r--pyload/webui/themes/Dark/img/cog.png (renamed from module/web/media/default/img/cog.png)bin512 -> 512 bytes
-rw-r--r--pyload/webui/themes/Dark/img/control_add.png (renamed from module/web/media/default/img/control_add.png)bin446 -> 446 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/control_cancel.png (renamed from module/web/media/default/img/control_cancel.png)bin3349 -> 3349 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/control_pause.png (renamed from module/web/media/default/img/control_pause.png)bin598 -> 598 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/control_play.png (renamed from module/web/media/default/img/control_play.png)bin592 -> 592 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/control_stop.png (renamed from module/web/media/default/img/control_stop.png)bin403 -> 403 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/dark-bg.jpgbin0 -> 40930 bytes
-rw-r--r--pyload/webui/themes/Dark/img/delete.png (renamed from module/web/media/default/img/delete.png)bin715 -> 715 bytes
-rw-r--r--pyload/webui/themes/Dark/img/drag_corner.gif (renamed from module/web/media/default/img/drag_corner.gif)bin76 -> 76 bytes
-rw-r--r--pyload/webui/themes/Dark/img/error.png (renamed from module/web/media/default/img/error.png)bin701 -> 701 bytes
-rw-r--r--pyload/webui/themes/Dark/img/folder.png (renamed from module/web/media/default/img/folder.png)bin537 -> 537 bytes
-rw-r--r--pyload/webui/themes/Dark/img/full.png (renamed from module/web/media/default/img/full.png)bin3543 -> 3543 bytes
-rw-r--r--pyload/webui/themes/Dark/img/head-login.png (renamed from module/web/media/default/img/head-login.png)bin1288 -> 1288 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/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/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/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/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/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/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/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/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/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/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/head_bg1.png (renamed from module/web/media/default/img/head_bg1.png)bin125 -> 125 bytes
-rw-r--r--pyload/webui/themes/Dark/img/images.png (renamed from module/web/media/default/img/images.png)bin661 -> 661 bytes
-rw-r--r--pyload/webui/themes/Dark/img/notice.png (renamed from module/web/media/default/img/notice.png)bin778 -> 778 bytes
-rw-r--r--pyload/webui/themes/Dark/img/package_go.png (renamed from module/web/media/default/img/package_go.png)bin898 -> 898 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/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/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/parseUri.png (renamed from module/web/media/default/img/parseUri.png)bin666 -> 666 bytes
-rw-r--r--pyload/webui/themes/Dark/img/pencil.png (renamed from module/web/media/default/img/pencil.png)bin450 -> 450 bytes
-rw-r--r--pyload/webui/themes/Dark/img/pyload-logo.pngbin0 -> 6947 bytes
-rw-r--r--pyload/webui/themes/Dark/img/reconnect.png (renamed from module/web/media/default/img/reconnect.png)bin755 -> 755 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_None.png (renamed from module/web/media/default/img/status_None.png)bin7613 -> 7613 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_downloading.png (renamed from module/web/media/default/img/status_downloading.png)bin943 -> 943 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_failed.png (renamed from module/web/media/default/img/status_failed.png)bin701 -> 701 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_finished.png (renamed from module/web/media/default/img/status_finished.png)bin781 -> 781 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_offline.png (renamed from module/web/media/default/img/status_offline.png)bin700 -> 700 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_proc.png (renamed from module/web/media/default/img/status_proc.png)bin512 -> 512 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_queue.png (renamed from module/web/media/default/img/status_queue.png)bin7613 -> 7613 bytes
-rw-r--r--pyload/webui/themes/Dark/img/status_waiting.png (renamed from module/web/media/default/img/status_waiting.png)bin889 -> 889 bytes
-rw-r--r--pyload/webui/themes/Dark/img/success.png (renamed from module/web/media/default/img/success.png)bin781 -> 781 bytes
-rw-r--r--pyload/webui/themes/Dark/img/tab-background.pngbin0 -> 3044 bytes
-rw-r--r--pyload/webui/themes/Dark/img/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/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/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/user-info.png (renamed from module/web/media/default/img/user-info.png)bin3963 -> 3963 bytes
-rw-r--r--pyload/webui/themes/Dark/js/admin.coffee58
-rw-r--r--pyload/webui/themes/Dark/js/admin.min.js3
-rw-r--r--pyload/webui/themes/Dark/js/base.coffee177
-rw-r--r--pyload/webui/themes/Dark/js/base.min.js3
-rw-r--r--pyload/webui/themes/Dark/js/package.js376
-rw-r--r--pyload/webui/themes/Dark/js/settings.coffee107
-rw-r--r--pyload/webui/themes/Dark/js/settings.min.js3
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png (renamed from module/web/media/img/dialog-close.png)bin689 -> 689 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png (renamed from module/web/media/img/dialog-question.png)bin2073 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js43
-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/base.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.css73
-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/admin.coffee58
-rw-r--r--pyload/webui/themes/Default/js/admin.min.js3
-rw-r--r--pyload/webui/themes/Default/js/base.coffee177
-rw-r--r--pyload/webui/themes/Default/js/base.min.js3
-rw-r--r--pyload/webui/themes/Default/js/package.js376
-rw-r--r--pyload/webui/themes/Default/js/settings.coffee107
-rw-r--r--pyload/webui/themes/Default/js/settings.min.js3
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js43
-rw-r--r--pyload/webui/themes/Default/tml/admin.html98
-rw-r--r--pyload/webui/themes/Default/tml/base.html177
-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.html76
-rw-r--r--pyload/webui/themes/Default/tml/folder.html15
-rw-r--r--pyload/webui/themes/Default/tml/home.html263
-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.css85
-rw-r--r--pyload/webui/themes/Flat/css/base.css866
-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.css70
-rw-r--r--pyload/webui/themes/Flat/img/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/Flat/img/ajax-loader.gifbin0 -> 404 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/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/Flat/img/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/Flat/img/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/Flat/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/Flat/img/closebtn.gifbin0 -> 254 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/delete.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/Flat/img/drag_corner.gifbin0 -> 76 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/full.pngbin0 -> 3543 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-recent.pngbin0 -> 932 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/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/Flat/img/images.pngbin0 -> 661 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/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/Flat/img/pencil.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/Flat/img/pyload-logo.pngbin0 -> 8457 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/tab-background.pngbin0 -> 179 bytes
-rw-r--r--pyload/webui/themes/Flat/img/tabs-border-bottom.pngbin0 -> 163 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/admin.coffee58
-rw-r--r--pyload/webui/themes/Flat/js/admin.min.js3
-rw-r--r--pyload/webui/themes/Flat/js/base.coffee177
-rw-r--r--pyload/webui/themes/Flat/js/base.min.js3
-rw-r--r--pyload/webui/themes/Flat/js/filemanager.js291
-rw-r--r--pyload/webui/themes/Flat/js/package.js376
-rw-r--r--pyload/webui/themes/Flat/js/settings.coffee107
-rw-r--r--pyload/webui/themes/Flat/js/settings.min.js3
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js43
-rw-r--r--pyload/webui/themes/Flat/tml/admin.html98
-rw-r--r--pyload/webui/themes/Flat/tml/base.html180
-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/filemanager.html78
-rw-r--r--pyload/webui/themes/Flat/tml/folder.html15
-rw-r--r--pyload/webui/themes/Flat/tml/home.html266
-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.html48
-rw-r--r--pyload/webui/themes/Flat/tml/window.html46
-rw-r--r--pyload/webui/themes/Next/css/MooDialog.css92
-rw-r--r--pyload/webui/themes/Next/css/log.css112
-rw-r--r--pyload/webui/themes/Next/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/Next/css/window.css (renamed from module/web/media/default/css/window.css)0
-rw-r--r--pyload/webui/themes/Next/img/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/Next/img/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/Next/img/arrow_refresh.pngbin0 -> 685 bytes
-rw-r--r--pyload/webui/themes/Next/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/Next/img/control_cancel.pngbin0 -> 3349 bytes
-rw-r--r--pyload/webui/themes/Next/img/delete.pngbin0 -> 715 bytes
-rw-r--r--pyload/webui/themes/Next/img/package_go.pngbin0 -> 898 bytes
-rw-r--r--pyload/webui/themes/Next/img/pencil.pngbin0 -> 450 bytes
-rw-r--r--pyload/webui/themes/Next/img/pyload-logo.pngbin0 -> 8457 bytes
-rw-r--r--pyload/webui/themes/Next/js/admin.coffee (renamed from module/web/media/js/admin.coffee)0
-rw-r--r--pyload/webui/themes/Next/js/admin.js (renamed from module/web/media/js/admin.js)0
-rw-r--r--pyload/webui/themes/Next/js/base.coffee173
-rw-r--r--pyload/webui/themes/Next/js/base.js (renamed from module/web/media/js/base.js)0
-rw-r--r--pyload/webui/themes/Next/js/filemanager.js291
-rw-r--r--pyload/webui/themes/Next/js/package.js397
-rw-r--r--pyload/webui/themes/Next/js/settings.coffee107
-rw-r--r--pyload/webui/themes/Next/js/settings.js3
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css476
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map1
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css5
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css6566
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map1
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css5
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eotbin0 -> 20127 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg288
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttfbin0 -> 45404 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woffbin0 -> 23424 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2bin0 -> 18028 bytes
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js2306
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js7
-rw-r--r--pyload/webui/themes/Next/lib/Bootstrap/js/npm.js13
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js45
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js80
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js21
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js47
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js33
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js48
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js37
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js140
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js137
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css95
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css66
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js6068
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/MooTools-More.js14345
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/Purr/purr.js309
-rw-r--r--pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js43
-rw-r--r--pyload/webui/themes/Next/tml/admin.html100
-rw-r--r--pyload/webui/themes/Next/tml/base.html199
-rw-r--r--pyload/webui/themes/Next/tml/captcha.html40
-rw-r--r--pyload/webui/themes/Next/tml/downloads.html29
-rw-r--r--pyload/webui/themes/Next/tml/filemanager.html80
-rw-r--r--pyload/webui/themes/Next/tml/folder.html15
-rw-r--r--pyload/webui/themes/Next/tml/home.html277
-rw-r--r--pyload/webui/themes/Next/tml/info.html81
-rw-r--r--pyload/webui/themes/Next/tml/login.html36
-rw-r--r--pyload/webui/themes/Next/tml/logout.html9
-rw-r--r--pyload/webui/themes/Next/tml/logs.html41
-rw-r--r--pyload/webui/themes/Next/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/Next/tml/queue.html109
-rw-r--r--pyload/webui/themes/Next/tml/settings.html212
-rw-r--r--pyload/webui/themes/Next/tml/settings_item.html50
-rw-r--r--pyload/webui/themes/Next/tml/window.html46
-rw-r--r--scripts/Readme.txt25
-rwxr-xr-xsetup.py99
-rw-r--r--systemCheck.py142
-rw-r--r--testlinks.txt26
-rw-r--r--tests/APIExerciser.py164
-rw-r--r--tests/clonedigger.sh4
-rw-r--r--tests/code_analysis.sh15
-rw-r--r--tests/test_api.py18
-rw-r--r--tests/test_json.py41
2019 files changed, 219711 insertions, 68818 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..14e8aa543
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,83 @@
+# Common packaging directories
+bin/
+build/
+dist/
+env/
+lib64/
+parts/
+sdist/
+var/
+temp/
+
+# 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]
+
+# 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/
+paver-minilib.zip
+/setup.sh
+/tesseract/
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..1460141ae
--- /dev/null
+++ b/CREDITS.md
@@ -0,0 +1,50 @@
+Credits
+-------
+
+### pyLoad Team ###
+
+*(alphabetically sorted)*
+
+ - RaNaN <Mast3rRaNaN@hotmail.de>
+ - himbrr <himbrr@himbrr.ws>
+ - mkaay (Marius) <mkaay@mkaay.de>
+ - sebnapi
+ - spoob <spoob@gmx.de>
+ - stickell (Stefano) <l.stickell@yahoo.it>
+ - vuolter (Walter Purcaro) <vuolter@gmail.com>
+ - zoidberg10 <zoidberg@mujmail.cz>
+
+A special thanks to spoob, sebnapi and RaNaN who created pyLoad!
+
+
+### Mantainers ###
+
+*(alphabetically sorted)*
+
+ - stickell (Stefano) <l.stickell@yahoo.it>
+ - vuolter (Walter Purcaro) <vuolter@gmail.com>
+
+
+### 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! ####
+
+
+-----------------------------------
+###### pyLoad Team 2008-2015 ######
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..af224e7f7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,270 @@
+<p align="center"><a href="http://pyload.org/"><img src="/docs/resources/banner.png" alt="pyLoad" /></a></p>
+
+[![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 in Python and designed to be extremely lightweight.
+
+
+Table of contents
+-----------------
+
+ - [Description](#description)
+ - [Download](#download)
+ - [Installation](#installation)
+ - [Dependencies](#dependencies)
+ - [Required](#required)
+ - [Optional](#optional)
+ - [Usage](#usage)
+ - [First Start](#first-start)
+ - [Web User Interface](#web-user-interface)
+ - [Command Line Interface](#command-line-interface)
+ - [Development](#development)
+ - [Translations](#translations)
+ - [Send a tip for translators](#send-a-tip-for-translators)
+ - [Update templates](#update-templates)
+ - [Retrieve PO files](#retrieve-po-files)
+ - [Compile PO files](#compile-po-files)
+ - [Licensing](#licensing)
+ - [Main program](#main-program)
+ - [Plugins](#plugins)
+ - [Plugin policy](#plugin-policy)
+ - [Credits](#credits)
+
+
+Description
+-----------
+
+**pyLoad** was developed to run on any device able to connect to internet and supporting the Python programming language.
+That's mean it's available for a really wide range of hardware platforms and operating systems.
+You can control it entirely by web using its friendly Web User Interface.
+
+All common video-sites, one-click-hosters, container formats and well known web standards are supported to allow you to download your 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 Application Programming Interface, easily extendable and accessible by external tools, cross-platform apps or other softwares.
+Just take a look to the [Development section](#development) for more info about that.
+
+For news, wiki, forum and further info visit the pyLoad website: <http://pyload.org/>.
+
+Or joining us at `#pyload` on `irc.freenode.net`.
+
+
+Download
+--------
+
+> **Note:**
+> If you wanna use pyLoad on Windows, it's hightly recommented to install the latest **official** pre-build package for that platform.
+
+Releases | Download
+----------------------------------------------------- | -----------------------------------------------------
+Pre-build packages with changelog | <https://github.com/pyload/pyload/releases>
+
+Pre-build packages are provided with all the software dependencies required to run pyLoad flawlessly on the referenced platform.
+If you choose a source code, at least you need to have the proper Python version installed on your platform before launch pyLoad.
+
+Source code | Download
+----------------------------------------------------- | -----------------------------------------------------
+Latest stable version | <https://github.com/pyload/pyload/archive/stable.zip>
+Latest development version | <https://github.com/pyload/pyload/archive/master.zip>
+
+
+Installation
+------------
+
+pyLoad currently works under:
+
+ - [x] **Python 2.5**
+ - [x] **Python 2.6**
+ - [x] **Python 2.7**
+ - [ ] Python 3
+ - [ ] PyPy
+
+You can install any missing software package from the *Python Package Index* typing:
+
+ pip install <package-name>
+
+
+Dependencies
+------------
+
+### Required ###
+
+ - **Beaker**
+ - **Getch**
+ - **MultipartPostHandler**
+ - **SafeEval**
+ - **bottle**
+ - **colorama**
+ - **jinja2**
+ - **markupsafe**
+ - **pycurl** (python-curl)
+ - **rename_process**
+ - **setuptools**
+ - **thrift**
+ - **wsgiserver**
+
+
+Some extra features require additional software packages. See below:
+
+### Optional ###
+
+ - **BeautifulSoup** *Few plugins support*
+ - **PIL** (python-imaging) *Captcha recognition*
+ - **Send2Trash** *Trash support*
+ - **colorlog** *Colored log*
+ - **bjoern** (<https://github.com/jonashaag/bjoern>) *More responsive web interface*
+ - **node.js** *ClickNLoad and other plugins*
+ - or **ossp-js**
+ - or **pyv8**
+ - or **rhino**
+ - or **spidermonkey**
+ - **pyOpenSSL** *SSL support*
+ - **pycrypto** *RSDF/CCF/DLC decrypting*
+ - **simplejson** *JSON speedup*
+ - **tesseract** *Captcha OCR support*
+
+
+Usage
+-----
+
+### First Start ###
+
+Run:
+
+ python pyload.py
+
+and follow the setup assistant instructions.
+
+> **Note:**
+> If you have installed pyLoad by a package-manager, command `python pyload.py` might be equivalent to `pyload`.
+
+If something go wrong, you can restart the setup assistant typing:
+
+ python pyload.py -s
+
+Or you can even edit the configuration files located in your pyLoad home directory
+(usually `%userprofile%/pyload` on Windows or `~/.pyload` otherwise) 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 browser to the socket address configured by setup (default to `http://localhost:8000`).
+
+You can get a list of accepted arguments typing:
+
+ python pyload.py -h
+
+
+Development
+-----------
+
+ * pyLoad roadmap: <https://github.com/pyload/pyload/milestones>.
+ * To report bugs, suggest features, ask for a question or help us out, visit: <https://github.com/pyload/pyload/issues>.
+ * To request the merge of your code in the pyLoad repository, open a new *pull request* here: <https://github.com/pyload/pyload/pulls>.
+ * Documentation about how extending pyLoad can be found at: <http://docs.pyload.org>.
+
+
+Translations
+------------
+
+The localization process take place on Crowdin: <http://crowdin.net/project/pyload>.
+
+
+### Send 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
+
+
+### Update templates ###
+
+To update POT files type:
+
+ paver generate_locale
+
+To automatically upload the updated POTs on Crowdin for the localization process just type:
+
+ 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 the administrators.
+
+
+### Retrieve PO files ###
+
+Updated PO files can be automatically download from Crowdin typing:
+
+ 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 typing:
+
+ paver compile_translations
+
+To compile a single file just use command `msgfmt`.
+For example to compile a core.po file type:
+
+ msgfmt -o core.mo core.po
+
+
+Licensing
+---------
+
+### Main program ###
+
+Please refer to the attached [LICENSE.md](/LICENSE.md) for the extended license.
+
+
+### Plugins ###
+
+According to the terms of the pyLoad license, official plugins must be treated as an extension of the main program
+and released under the same license of pyLoad or a compatible one:
+
+ * Any plugin published **without the \_\_license\_\_ attribute** is implied published under the same license of pyLoad.
+ * Only plugins published **with a compatible license** will be accepted as official and included in the pyLoad repository.
+ * **Un-official plugins are not supported**, so any issue report or feature request regarding this kind of plugin will be rejected.
+ * Is recommended to put a **shorten** license notice only if nedfull to avoid misunderstandings about the adopted license.
+
+
+Plugin policy
+-------------
+
+ - No cracking website or service
+ - No drugs website or service
+ - No e-commerce website or service
+ - No government website or service
+ - No illegal website or service in the country where its servers are located
+ - No pedopornography website or service
+ - No private website or service
+ - No racist website or service
+ - No warez website or service
+ - No weapon website or service
+ - ...
+
+
+Credits
+-------
+
+Please refer to the attached [CREDITS.md](/CREDITS.md) for the extended credits.
+
+
+-----------------------------------
+###### pyLoad Team 2008-2015 ######
diff --git a/docs/Makefile b/docs/Makefile
index 2082e6ef3..01a0119fd 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -15,116 +15,116 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
- -rm -rf $(BUILDDIR)/*
+ -rm -rf $(BUILDDIR)/*
html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyLoad.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyLoad.qhc"
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyLoad.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyLoad.qhc"
devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/pyLoad"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyLoad"
- @echo "# devhelp"
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/pyLoad"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyLoad"
+ @echo "# devhelp"
epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- make -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ make -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/access_api.rst b/docs/access_api.rst
index df69da8b2..1e19ea71b 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,11 +48,11 @@ 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")
- except:
+ except Exception:
print "Login was wrong"
exit()
@@ -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/build_docs.py b/docs/build_docs.py
new file mode 100644
index 000000000..05f34be44
--- /dev/null
+++ b/docs/build_docs.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# pyLoad documentation build configuration file, created by
+# sphinx-quickstart on Sat Jun 4 11:54:34 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import os
+import platform
+import sys
+
+
+dir_name = os.path.join(os.path.dirname(os.path.abspath("")))
+
+sys.path.append(dir_name)
+
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest',
+ 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig',
+ 'sphinx.ext.viewcode']
+
+autosummary_generate = True
+autodoc_default_flags = ['members']
+autoclass_content = 'both'
+autodoc_member_order = 'bysource'
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'pyLoad'
+copyright = u'2008-2015, 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
+# built documents.
+#
+# The full version, including alpha/beta/rc tags.
+"""
+[[[cog
+from pavement import options
+v = options.version.split(".")
+cog.outl("version = '%s'" % ".".join(v[:2]))
+cog.outl("release = '%s'" % ".".join(v))
+]]]
+"""
+version = '0.4'
+release = '0.4.10'
+# [[[end]]]
+
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = os.path.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 = os.path.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,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pyLoaddoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+# latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+# latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'pyLoad.tex', u'pyLoad Documentation',
+ u'pyLoad Team', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+# latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'pyload', u'pyLoad Documentation',
+ [u'pyLoad Team'], 1)
+]
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index 9d2cf98f9..000000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,238 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# pyLoad documentation build configuration file, created by
-# sphinx-quickstart on Sat Jun 4 11:54:34 2011.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-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"))
-
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
-
-autosummary_generate = True
-autodoc_default_flags = ['members']
-autoclass_content = 'both'
-autodoc_member_order = 'bysource'
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'pyLoad'
-copyright = u'2011, 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
-# built documents.
-#
-# The full version, including alpha/beta/rc tags.
-""" [[[cog
-from pavement import options
-v = options.version.split(".")
-cog.outl("version = '%s'" % ".".join(v[:2]))
-cog.outl("release = '%s'" % ".".join(v))
-]]]"""
-version = '0.4'
-release = '0.4.9'
-# [[[end]]]
-
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# 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")
-
-# 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")
-
-# 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,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'pyLoaddoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'pyLoad.tex', u'pyLoad Documentation',
- u'pyLoad Team', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'pyload', u'pyLoad Documentation',
- [u'pyLoad Team'], 1)
-]
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/docs/docs.conf b/docs/docs.conf
index e197cfa43..7eb8ee005 100644
--- a/docs/docs.conf
+++ b/docs/docs.conf
@@ -1,14 +1,15 @@
-# usage: epydoc --conf docs.conf , results goes to ~/.pyload/docs
+#@NOTE: usage: epydoc --conf docs.conf
+# results goes to ~/.pyload/docs
[epydoc]
-modules: pyLoadCore.py, pyLoadCli.py, pyloadGui.py, module
+modules: pyload.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: remote\thriftbackend\thriftgen|plugin\(account|addon|container|crypter|hook|hoster|internal|ocr)|.pyc|.pyo
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..c6f19d7a0 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.plugin.Plugin.Base
+ pyload.plugin.Plugin.Plugin
+ pyload.plugin.Crypter.Crypter
+ pyload.plugin.Account.Account
+ pyload.plugin.Addon.Addon
+ pyload.manager.Addon.AddonManager
+ pyload.Datatype.File.PyFile
+ pyload.Datatype.Package.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/setup.cfg b/docs/setup.cfg
index 51c054a75..51c054a75 100644
--- a/setup.cfg
+++ b/docs/setup.cfg
diff --git a/docs/write_addons.rst b/docs/write_addons.rst
new file mode 100644
index 000000000..6fc77abee
--- /dev/null
+++ b/docs/write_addons.rst
@@ -0,0 +1,172 @@
+.. _write_addons:
+
+Addons
+======
+
+A Addon is a python file which is located at :file:`pyload/plugin/addon`.
+The :class:`AddonManager <pyload.manager.Addon.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.Addon.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.plugin.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.plugin.Addon import Addon
+
+
+ class YourAddon(Addon):
+ __name = "YourAddon"
+ __tupe = "addon"
+ __version = "0.1"
+
+ __config = [("activated", "bool", "Activated", "True")]
+
+ __description = "Does really cool stuff"
+ __license = "Your license short name"
+ __authors = [("Me", "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.plugin.Addon.Addon>` base class.
+The name is indicating when the function gets called.
+See :class:`Addon <pyload.plugin.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.File.PyFile>`
+or :class:`PyPackage <pyload.Datatype.Package.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.plugin.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.Addon.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.plugin.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.Addon.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.plugin.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.plugin.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/plugin/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..0b568f74c 100644
--- a/docs/write_plugins.rst
+++ b/docs/write_plugins.rst
@@ -3,15 +3,15 @@
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/plugin/`. 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.plugin.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
-the plugin. In case the ``__pattern__`` matched on the url the plugin will be assigned to handle it and instanciated when
+Every plugin defines a ``__pattern`` and when the user adds urls, every url is matched against the pattern defined in
+the plugin. In case the ``__pattern`` matched on the url the plugin will be assigned to handle it and instanciated when
pyLoad begins to download/decrypt the url.
Plugin header
@@ -19,18 +19,19 @@ Plugin header
How basic hoster plugin header could look like: ::
- from module.plugin.Hoster import Hoster
+ from pyload.plugin.Hoster import Hoster
+
class MyFileHoster(Hoster):
- __name__ = "MyFileHoster"
- __version__ = "0.1"
- __pattern__ = r"http://myfilehoster.example.com/file_id/[0-9]+"
- __config__ = []
+ __name = "MyFileHoster"
+ __version = "0.1"
+ __pattern = r"http://myfilehoster.example.com/file_id/[0-9]+"
+ __config = []
-You have to define these meta-data, ``__pattern__`` has to be a regexp that sucessfully compiles with
-``re.compile(__pattern__)``.
+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,15 +42,16 @@ 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.plugin.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
+ html = self.load(pyfile.url) #: load the content of the orginal pyfile.url to html
# parse the name from the site and set attribute in pyfile
pyfile.name = self.myFunctionToParseTheName(html)
@@ -58,7 +60,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.File.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 +73,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.plugin.Addon.Addon>` plugins
Crypter
-------
@@ -81,23 +83,25 @@ Well, they work nearly the same, only that the function they have to provide is
Example: ::
- from module.plugin.Crypter import Crypter
+ from pyload.plugin.Crypter import Crypter
+
class MyFileCrypter(Crypter):
"""
plugin code
"""
+
def decrypt(self, pyfile):
urls = ["http://get.pyload.org/src", "http://get.pyload.org/debian", "http://get.pyload.org/win"]
- self.packages.append(("pyLoad packages", urls, "pyLoad packages")) # urls list of urls
+ 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.plugin.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/plugin/`.
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/module/lib/BeautifulSoup.py b/lib/Python/Lib/BeautifulSoup.py
index 55567f588..55567f588 100644
--- a/module/lib/BeautifulSoup.py
+++ b/lib/Python/Lib/BeautifulSoup.py
diff --git a/lib/Python/Lib/Crypto/Cipher/AES.py b/lib/Python/Lib/Crypto/Cipher/AES.py
new file mode 100644
index 000000000..14f68d8ca
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/AES.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/AES.py : AES
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""AES symmetric cipher
+
+AES `(Advanced Encryption Standard)`__ is a symmetric block cipher standardized
+by NIST_ . It has a fixed data block size of 16 bytes.
+Its keys can be 128, 192, or 256 bits long.
+
+AES is very fast and secure, and it is the de facto standard for symmetric
+encryption.
+
+As an example, encryption can be done as follows:
+
+ >>> from Crypto.Cipher import AES
+ >>> from Crypto import Random
+ >>>
+ >>> key = b'Sixteen byte key'
+ >>> iv = Random.new().read(AES.block_size)
+ >>> cipher = AES.new(key, AES.MODE_CFB, iv)
+ >>> msg = iv + cipher.encrypt(b'Attack at dawn')
+
+.. __: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
+.. _NIST: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import blockalgo
+from Crypto.Cipher import _AES
+
+class AESCipher (blockalgo.BlockAlgo):
+ """AES cipher object"""
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize an AES cipher object
+
+ See also `new()` at the module level."""
+ blockalgo.BlockAlgo.__init__(self, _AES, key, *args, **kwargs)
+
+def new(key, *args, **kwargs):
+ """Create a new AES cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ It must be 16 (*AES-128*), 24 (*AES-192*), or 32 (*AES-256*) bytes long.
+ :Keywords:
+ mode : a *MODE_** constant
+ The chaining mode to use for encryption or decryption.
+ Default is `MODE_ECB`.
+ IV : byte string
+ The initialization vector to use for encryption or decryption.
+
+ It is ignored for `MODE_ECB` and `MODE_CTR`.
+
+ For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
+ and `block_size` +2 bytes for decryption (in the latter case, it is
+ actually the *encrypted* IV which was prefixed to the ciphertext).
+ It is mandatory.
+
+ For all other modes, it must be `block_size` bytes longs. It is optional and
+ when not present it will be given a default value of all zeroes.
+ counter : callable
+ (*Only* `MODE_CTR`). A stateful function that returns the next
+ *counter block*, which is a byte string of `block_size` bytes.
+ For better performance, use `Crypto.Util.Counter`.
+ segment_size : integer
+ (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext
+ are segmented in.
+ It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8.
+
+ :Return: an `AESCipher` object
+ """
+ return AESCipher(key, *args, **kwargs)
+
+#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`.
+MODE_ECB = 1
+#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`.
+MODE_CBC = 2
+#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`.
+MODE_CFB = 3
+#: This mode should not be used.
+MODE_PGP = 4
+#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`.
+MODE_OFB = 5
+#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`.
+MODE_CTR = 6
+#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`.
+MODE_OPENPGP = 7
+#: Size of a data block (in bytes)
+block_size = 16
+#: Size of a key (in bytes)
+key_size = ( 16, 24, 32 )
+
diff --git a/lib/Python/Lib/Crypto/Cipher/ARC2.py b/lib/Python/Lib/Crypto/Cipher/ARC2.py
new file mode 100644
index 000000000..7b5f43aa7
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/ARC2.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/ARC2.py : ARC2.py
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""RC2 symmetric cipher
+
+RC2_ (Rivest's Cipher version 2) is a symmetric block cipher designed
+by Ron Rivest in 1987. The cipher started as a proprietary design,
+that was reverse engineered and anonymously posted on Usenet in 1996.
+For this reason, the algorithm was first called *Alleged* RC2 (ARC2),
+since the company that owned RC2 (RSA Data Inc.) did not confirm whether
+the details leaked into public domain were really correct.
+
+The company eventually published its full specification in RFC2268_.
+
+RC2 has a fixed data block size of 8 bytes. Length of its keys can vary from
+8 to 128 bits. One particular property of RC2 is that the actual
+cryptographic strength of the key (*effective key length*) can be reduced
+via a parameter.
+
+Even though RC2 is not cryptographically broken, it has not been analyzed as
+thoroughly as AES, which is also faster than RC2.
+
+New designs should not use RC2.
+
+As an example, encryption can be done as follows:
+
+ >>> from Crypto.Cipher import ARC2
+ >>> from Crypto import Random
+ >>>
+ >>> key = b'Sixteen byte key'
+ >>> iv = Random.new().read(ARC2.block_size)
+ >>> cipher = ARC2.new(key, ARC2.MODE_CFB, iv)
+ >>> msg = iv + cipher.encrypt(b'Attack at dawn')
+
+.. _RC2: http://en.wikipedia.org/wiki/RC2
+.. _RFC2268: http://tools.ietf.org/html/rfc2268
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import blockalgo
+from Crypto.Cipher import _ARC2
+
+class RC2Cipher (blockalgo.BlockAlgo):
+ """RC2 cipher object"""
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize an ARC2 cipher object
+
+ See also `new()` at the module level."""
+ blockalgo.BlockAlgo.__init__(self, _ARC2, key, *args, **kwargs)
+
+def new(key, *args, **kwargs):
+ """Create a new RC2 cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ Its length can vary from 1 to 128 bytes.
+ :Keywords:
+ mode : a *MODE_** constant
+ The chaining mode to use for encryption or decryption.
+ Default is `MODE_ECB`.
+ IV : byte string
+ The initialization vector to use for encryption or decryption.
+
+ It is ignored for `MODE_ECB` and `MODE_CTR`.
+
+ For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
+ and `block_size` +2 bytes for decryption (in the latter case, it is
+ actually the *encrypted* IV which was prefixed to the ciphertext).
+ It is mandatory.
+
+ For all other modes, it must be `block_size` bytes longs. It is optional and
+ when not present it will be given a default value of all zeroes.
+ counter : callable
+ (*Only* `MODE_CTR`). A stateful function that returns the next
+ *counter block*, which is a byte string of `block_size` bytes.
+ For better performance, use `Crypto.Util.Counter`.
+ segment_size : integer
+ (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext
+ are segmented in.
+ It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8.
+ effective_keylen : integer
+ Maximum cryptographic strength of the key, in bits.
+ It can vary from 0 to 1024. The default value is 1024.
+
+ :Return: an `RC2Cipher` object
+ """
+ return RC2Cipher(key, *args, **kwargs)
+
+#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`.
+MODE_ECB = 1
+#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`.
+MODE_CBC = 2
+#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`.
+MODE_CFB = 3
+#: This mode should not be used.
+MODE_PGP = 4
+#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`.
+MODE_OFB = 5
+#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`.
+MODE_CTR = 6
+#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`.
+MODE_OPENPGP = 7
+#: Size of a data block (in bytes)
+block_size = 8
+#: Size of a key (in bytes)
+key_size = xrange(1,16+1)
+
diff --git a/lib/Python/Lib/Crypto/Cipher/ARC4.py b/lib/Python/Lib/Crypto/Cipher/ARC4.py
new file mode 100644
index 000000000..b745e7ce6
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/ARC4.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/ARC4.py : ARC4
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""ARC4 symmetric cipher
+
+ARC4_ (Alleged RC4) is an implementation of RC4 (Rivest's Cipher version 4),
+a symmetric stream cipher designed by Ron Rivest in 1987.
+
+The cipher started as a proprietary design, that was reverse engineered and
+anonymously posted on Usenet in 1994. The company that owns RC4 (RSA Data
+Inc.) never confirmed the correctness of the leaked algorithm.
+
+Unlike RC2, the company has never published the full specification of RC4,
+of whom it still holds the trademark.
+
+ARC4 keys can vary in length from 40 to 2048 bits.
+
+One problem of ARC4 is that it does not take a nonce or an IV. If it is required
+to encrypt multiple messages with the same long-term key, a distinct
+independent nonce must be created for each message, and a short-term key must
+be derived from the combination of the long-term key and the nonce.
+Due to the weak key scheduling algorithm of RC2, the combination must be carried
+out with a complex function (e.g. a cryptographic hash) and not by simply
+concatenating key and nonce.
+
+New designs should not use ARC4. A good alternative is AES
+(`Crypto.Cipher.AES`) in any of the modes that turn it into a stream cipher (OFB, CFB, or CTR).
+
+As an example, encryption can be done as follows:
+
+ >>> from Crypto.Cipher import ARC4
+ >>> from Crypto.Hash import SHA
+ >>> from Crypto import Random
+ >>>
+ >>> key = b'Very long and confidential key'
+ >>> nonce = Random.new().read(16)
+ >>> tempkey = SHA.new(key+nonce).digest()
+ >>> cipher = ARC4.new(tempkey)
+ >>> msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL')
+
+.. _ARC4: http://en.wikipedia.org/wiki/RC4
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import _ARC4
+
+class ARC4Cipher:
+ """ARC4 cipher object"""
+
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize an ARC4 cipher object
+
+ See also `new()` at the module level."""
+
+ self._cipher = _ARC4.new(key, *args, **kwargs)
+ self.block_size = self._cipher.block_size
+ self.key_size = self._cipher.key_size
+
+ def encrypt(self, plaintext):
+ """Encrypt a piece of data.
+
+ :Parameters:
+ plaintext : byte string
+ The piece of data to encrypt. It can be of any size.
+ :Return: the encrypted data (byte string, as long as the
+ plaintext).
+ """
+ return self._cipher.encrypt(plaintext)
+
+ def decrypt(self, ciphertext):
+ """Decrypt a piece of data.
+
+ :Parameters:
+ ciphertext : byte string
+ The piece of data to decrypt. It can be of any size.
+ :Return: the decrypted data (byte string, as long as the
+ ciphertext).
+ """
+ return self._cipher.decrypt(ciphertext)
+
+def new(key, *args, **kwargs):
+ """Create a new ARC4 cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ It can have any length, with a minimum of 40 bytes.
+ Its cryptograpic strength is always capped to 2048 bits (256 bytes).
+
+ :Return: an `ARC4Cipher` object
+ """
+ return ARC4Cipher(key, *args, **kwargs)
+
+#: Size of a data block (in bytes)
+block_size = 1
+#: Size of a key (in bytes)
+key_size = xrange(1,256+1)
+
diff --git a/lib/Python/Lib/Crypto/Cipher/Blowfish.py b/lib/Python/Lib/Crypto/Cipher/Blowfish.py
new file mode 100644
index 000000000..3f12bea26
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/Blowfish.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/Blowfish.py : Blowfish
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""Blowfish symmetric cipher
+
+Blowfish_ is a symmetric block cipher designed by Bruce Schneier.
+
+It has a fixed data block size of 8 bytes and its keys can vary in length
+from 32 to 448 bits (4 to 56 bytes).
+
+Blowfish is deemed secure and it is fast. However, its keys should be chosen
+to be big enough to withstand a brute force attack (e.g. at least 16 bytes).
+
+As an example, encryption can be done as follows:
+
+ >>> from Crypto.Cipher import Blowfish
+ >>> from Crypto import Random
+ >>> from struct import pack
+ >>>
+ >>> bs = Blowfish.block_size
+ >>> key = b'An arbitrarily long key'
+ >>> iv = Random.new().read(bs)
+ >>> cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv)
+ >>> plaintext = b'docendo discimus '
+ >>> plen = bs - divmod(len(plaintext),bs)[1]
+ >>> padding = [plen]*plen
+ >>> padding = pack('b'*plen, *padding)
+ >>> msg = iv + cipher.encrypt(plaintext + padding)
+
+.. _Blowfish: http://www.schneier.com/blowfish.html
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import blockalgo
+from Crypto.Cipher import _Blowfish
+
+class BlowfishCipher (blockalgo.BlockAlgo):
+ """Blowfish cipher object"""
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize a Blowfish cipher object
+
+ See also `new()` at the module level."""
+ blockalgo.BlockAlgo.__init__(self, _Blowfish, key, *args, **kwargs)
+
+def new(key, *args, **kwargs):
+ """Create a new Blowfish cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ Its length can vary from 4 to 56 bytes.
+ :Keywords:
+ mode : a *MODE_** constant
+ The chaining mode to use for encryption or decryption.
+ Default is `MODE_ECB`.
+ IV : byte string
+ The initialization vector to use for encryption or decryption.
+
+ It is ignored for `MODE_ECB` and `MODE_CTR`.
+
+ For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
+ and `block_size` +2 bytes for decryption (in the latter case, it is
+ actually the *encrypted* IV which was prefixed to the ciphertext).
+ It is mandatory.
+
+ For all other modes, it must be `block_size` bytes longs. It is optional and
+ when not present it will be given a default value of all zeroes.
+ counter : callable
+ (*Only* `MODE_CTR`). A stateful function that returns the next
+ *counter block*, which is a byte string of `block_size` bytes.
+ For better performance, use `Crypto.Util.Counter`.
+ segment_size : integer
+ (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext
+ are segmented in.
+ It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8.
+
+ :Return: a `BlowfishCipher` object
+ """
+ return BlowfishCipher(key, *args, **kwargs)
+
+#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`.
+MODE_ECB = 1
+#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`.
+MODE_CBC = 2
+#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`.
+MODE_CFB = 3
+#: This mode should not be used.
+MODE_PGP = 4
+#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`.
+MODE_OFB = 5
+#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`.
+MODE_CTR = 6
+#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`.
+MODE_OPENPGP = 7
+#: Size of a data block (in bytes)
+block_size = 8
+#: Size of a key (in bytes)
+key_size = xrange(4,56+1)
+
diff --git a/lib/Python/Lib/Crypto/Cipher/CAST.py b/lib/Python/Lib/Crypto/Cipher/CAST.py
new file mode 100644
index 000000000..f08dab3e0
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/CAST.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/CAST.py : CAST
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""CAST-128 symmetric cipher
+
+CAST-128_ (or CAST5) is a symmetric block cipher specified in RFC2144_.
+
+It has a fixed data block size of 8 bytes. Its key can vary in length
+from 40 to 128 bits.
+
+CAST is deemed to be cryptographically secure, but its usage is not widespread.
+Keys of sufficient length should be used to prevent brute force attacks
+(128 bits are recommended).
+
+As an example, encryption can be done as follows:
+
+ >>> from Crypto.Cipher import CAST
+ >>> from Crypto import Random
+ >>>
+ >>> key = b'Sixteen byte key'
+ >>> iv = Random.new().read(CAST.block_size)
+ >>> cipher = CAST.new(key, CAST.MODE_OPENPGP, iv)
+ >>> plaintext = b'sona si latine loqueris '
+ >>> msg = cipher.encrypt(plaintext)
+ >>>
+ ...
+ >>> eiv = msg[:CAST.block_size+2]
+ >>> ciphertext = msg[CAST.block_size+2:]
+ >>> cipher = CAST.new(key, CAST.MODE_OPENPGP, eiv)
+ >>> print cipher.decrypt(ciphertext)
+
+.. _CAST-128: http://en.wikipedia.org/wiki/CAST-128
+.. _RFC2144: http://tools.ietf.org/html/rfc2144
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import blockalgo
+from Crypto.Cipher import _CAST
+
+class CAST128Cipher(blockalgo.BlockAlgo):
+ """CAST-128 cipher object"""
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize a CAST-128 cipher object
+
+ See also `new()` at the module level."""
+ blockalgo.BlockAlgo.__init__(self, _CAST, key, *args, **kwargs)
+
+def new(key, *args, **kwargs):
+ """Create a new CAST-128 cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ Its length may vary from 5 to 16 bytes.
+ :Keywords:
+ mode : a *MODE_** constant
+ The chaining mode to use for encryption or decryption.
+ Default is `MODE_ECB`.
+ IV : byte string
+ The initialization vector to use for encryption or decryption.
+
+ It is ignored for `MODE_ECB` and `MODE_CTR`.
+
+ For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
+ and `block_size` +2 bytes for decryption (in the latter case, it is
+ actually the *encrypted* IV which was prefixed to the ciphertext).
+ It is mandatory.
+
+ For all other modes, it must be `block_size` bytes longs. It is optional and
+ when not present it will be given a default value of all zeroes.
+ counter : callable
+ (*Only* `MODE_CTR`). A stateful function that returns the next
+ *counter block*, which is a byte string of `block_size` bytes.
+ For better performance, use `Crypto.Util.Counter`.
+ segment_size : integer
+ (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext
+ are segmented in.
+ It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8.
+
+ :Return: an `CAST128Cipher` object
+ """
+ return CAST128Cipher(key, *args, **kwargs)
+
+#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`.
+MODE_ECB = 1
+#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`.
+MODE_CBC = 2
+#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`.
+MODE_CFB = 3
+#: This mode should not be used.
+MODE_PGP = 4
+#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`.
+MODE_OFB = 5
+#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`.
+MODE_CTR = 6
+#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`.
+MODE_OPENPGP = 7
+#: Size of a data block (in bytes)
+block_size = 8
+#: Size of a key (in bytes)
+key_size = xrange(5,16+1)
diff --git a/lib/Python/Lib/Crypto/Cipher/DES.py b/lib/Python/Lib/Crypto/Cipher/DES.py
new file mode 100644
index 000000000..2fae42fda
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/DES.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/DES.py : DES
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""DES symmetric cipher
+
+DES `(Data Encryption Standard)`__ is a symmetric block cipher standardized
+by NIST_ . It has a fixed data block size of 8 bytes.
+Its keys are 64 bits long, even though 8 bits were used for integrity (now they
+are ignored) and do not contribute to securty.
+
+DES is cryptographically secure, but its key length is too short by nowadays
+standards and it could be brute forced with some effort.
+
+DES should not be used for new designs. Use `AES`.
+
+As an example, encryption can be done as follows:
+
+ >>> from Crypto.Cipher import DES3
+ >>> from Crypto import Random
+ >>>
+ >>> key = b'Sixteen byte key'
+ >>> iv = Random.new().read(DES3.block_size)
+ >>> cipher = DES3.new(key, DES3.MODE_OFB, iv)
+ >>> plaintext = b'sona si latine loqueris '
+ >>> msg = iv + cipher.encrypt(plaintext)
+
+.. __: http://en.wikipedia.org/wiki/Data_Encryption_Standard
+.. _NIST: http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import blockalgo
+from Crypto.Cipher import _DES
+
+class DESCipher(blockalgo.BlockAlgo):
+ """DES cipher object"""
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize a DES cipher object
+
+ See also `new()` at the module level."""
+ blockalgo.BlockAlgo.__init__(self, _DES, key, *args, **kwargs)
+
+def new(key, *args, **kwargs):
+ """Create a new DES cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ It must be 8 byte long. The parity bits will be ignored.
+ :Keywords:
+ mode : a *MODE_** constant
+ The chaining mode to use for encryption or decryption.
+ Default is `MODE_ECB`.
+ IV : byte string
+ The initialization vector to use for encryption or decryption.
+
+ It is ignored for `MODE_ECB` and `MODE_CTR`.
+
+ For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
+ and `block_size` +2 bytes for decryption (in the latter case, it is
+ actually the *encrypted* IV which was prefixed to the ciphertext).
+ It is mandatory.
+
+ For all other modes, it must be `block_size` bytes longs. It is optional and
+ when not present it will be given a default value of all zeroes.
+ counter : callable
+ (*Only* `MODE_CTR`). A stateful function that returns the next
+ *counter block*, which is a byte string of `block_size` bytes.
+ For better performance, use `Crypto.Util.Counter`.
+ segment_size : integer
+ (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext
+ are segmented in.
+ It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8.
+
+ :Return: an `DESCipher` object
+ """
+ return DESCipher(key, *args, **kwargs)
+
+#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`.
+MODE_ECB = 1
+#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`.
+MODE_CBC = 2
+#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`.
+MODE_CFB = 3
+#: This mode should not be used.
+MODE_PGP = 4
+#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`.
+MODE_OFB = 5
+#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`.
+MODE_CTR = 6
+#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`.
+MODE_OPENPGP = 7
+#: Size of a data block (in bytes)
+block_size = 8
+#: Size of a key (in bytes)
+key_size = 8
diff --git a/lib/Python/Lib/Crypto/Cipher/DES3.py b/lib/Python/Lib/Crypto/Cipher/DES3.py
new file mode 100644
index 000000000..7fedac847
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/DES3.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/DES3.py : DES3
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""Triple DES symmetric cipher
+
+`Triple DES`__ (or TDES or TDEA or 3DES) is a symmetric block cipher standardized by NIST_.
+It has a fixed data block size of 8 bytes. Its keys are 128 (*Option 1*) or 192
+bits (*Option 2*) long.
+However, 1 out of 8 bits is used for redundancy and do not contribute to
+security. The effective key length is respectively 112 or 168 bits.
+
+TDES consists of the concatenation of 3 simple `DES` ciphers.
+
+The plaintext is first DES encrypted with *K1*, then decrypted with *K2*,
+and finally encrypted again with *K3*. The ciphertext is decrypted in the reverse manner.
+
+The 192 bit key is a bundle of three 64 bit independent subkeys: *K1*, *K2*, and *K3*.
+
+The 128 bit key is split into *K1* and *K2*, whereas *K1=K3*.
+
+It is important that all subkeys are different, otherwise TDES would degrade to
+single `DES`.
+
+TDES is cryptographically secure, even though it is neither as secure nor as fast
+as `AES`.
+
+As an example, encryption can be done as follows:
+
+ >>> from Crypto.Cipher import DES
+ >>> from Crypto import Random
+ >>> from Crypto.Util import Counter
+ >>>
+ >>> key = b'-8B key-'
+ >>> nonce = Random.new().read(DES.block_size/2)
+ >>> ctr = Counter.new(DES.block_size*8/2, prefix=nonce)
+ >>> cipher = DES.new(key, DES.MODE_CTR, counter=ctr)
+ >>> plaintext = b'We are no longer the knights who say ni!'
+ >>> msg = nonce + cipher.encrypt(plaintext)
+
+.. __: http://en.wikipedia.org/wiki/Triple_DES
+.. _NIST: http://csrc.nist.gov/publications/nistpubs/800-67/SP800-67.pdf
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import blockalgo
+from Crypto.Cipher import _DES3
+
+class DES3Cipher(blockalgo.BlockAlgo):
+ """TDES cipher object"""
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize a TDES cipher object
+
+ See also `new()` at the module level."""
+ blockalgo.BlockAlgo.__init__(self, _DES3, key, *args, **kwargs)
+
+def new(key, *args, **kwargs):
+ """Create a new TDES cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ It must be 16 or 24 bytes long. The parity bits will be ignored.
+ :Keywords:
+ mode : a *MODE_** constant
+ The chaining mode to use for encryption or decryption.
+ Default is `MODE_ECB`.
+ IV : byte string
+ The initialization vector to use for encryption or decryption.
+
+ It is ignored for `MODE_ECB` and `MODE_CTR`.
+
+ For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
+ and `block_size` +2 bytes for decryption (in the latter case, it is
+ actually the *encrypted* IV which was prefixed to the ciphertext).
+ It is mandatory.
+
+ For all other modes, it must be `block_size` bytes longs. It is optional and
+ when not present it will be given a default value of all zeroes.
+ counter : callable
+ (*Only* `MODE_CTR`). A stateful function that returns the next
+ *counter block*, which is a byte string of `block_size` bytes.
+ For better performance, use `Crypto.Util.Counter`.
+ segment_size : integer
+ (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext
+ are segmented in.
+ It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8.
+
+ :Attention: it is important that all 8 byte subkeys are different,
+ otherwise TDES would degrade to single `DES`.
+ :Return: an `DES3Cipher` object
+ """
+ return DES3Cipher(key, *args, **kwargs)
+
+#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`.
+MODE_ECB = 1
+#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`.
+MODE_CBC = 2
+#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`.
+MODE_CFB = 3
+#: This mode should not be used.
+MODE_PGP = 4
+#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`.
+MODE_OFB = 5
+#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`.
+MODE_CTR = 6
+#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`.
+MODE_OPENPGP = 7
+#: Size of a data block (in bytes)
+block_size = 8
+#: Size of a key (in bytes)
+key_size = ( 16, 24 )
diff --git a/lib/Python/Lib/Crypto/Cipher/PKCS1_OAEP.py b/lib/Python/Lib/Crypto/Cipher/PKCS1_OAEP.py
new file mode 100644
index 000000000..9afe176f1
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/PKCS1_OAEP.py
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/PKCS1_OAEP.py : PKCS#1 OAEP
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""RSA encryption protocol according to PKCS#1 OAEP
+
+See RFC3447__ or the `original RSA Labs specification`__ .
+
+This scheme is more properly called ``RSAES-OAEP``.
+
+As an example, a sender may encrypt a message in this way:
+
+ >>> from Crypto.Cipher import PKCS1_OAEP
+ >>> from Crypto.PublicKey import RSA
+ >>>
+ >>> message = 'To be encrypted'
+ >>> key = RSA.importKey(open('pubkey.der').read())
+ >>> cipher = PKCS1_OAEP.new(key)
+ >>> ciphertext = cipher.encrypt(message)
+
+At the receiver side, decryption can be done using the private part of
+the RSA key:
+
+ >>> key = RSA.importKey(open('privkey.der').read())
+ >>> cipher = PKCS1_OAP.new(key)
+ >>> message = cipher.decrypt(ciphertext)
+
+:undocumented: __revision__, __package__
+
+.. __: http://www.ietf.org/rfc/rfc3447.txt
+.. __: http://www.rsa.com/rsalabs/node.asp?id=2125.
+"""
+
+from __future__ import nested_scopes
+
+__revision__ = "$Id$"
+__all__ = [ 'new', 'PKCS1OAEP_Cipher' ]
+
+import Crypto.Signature.PKCS1_PSS
+import Crypto.Hash.SHA
+
+from Crypto.Util.py3compat import *
+import Crypto.Util.number
+from Crypto.Util.number import ceil_div
+from Crypto.Util.strxor import strxor
+
+class PKCS1OAEP_Cipher:
+ """This cipher can perform PKCS#1 v1.5 OAEP encryption or decryption."""
+
+ def __init__(self, key, hashAlgo, mgfunc, label):
+ """Initialize this PKCS#1 OAEP cipher object.
+
+ :Parameters:
+ key : an RSA key object
+ If a private half is given, both encryption and decryption are possible.
+ If a public half is given, only encryption is possible.
+ hashAlgo : hash object
+ The hash function to use. This can be a module under `Crypto.Hash`
+ or an existing hash object created from any of such modules. If not specified,
+ `Crypto.Hash.SHA` (that is, SHA-1) is used.
+ mgfunc : callable
+ A mask generation function that accepts two parameters: a string to
+ use as seed, and the lenth of the mask to generate, in bytes.
+ If not specified, the standard MGF1 is used (a safe choice).
+ label : string
+ A label to apply to this particular encryption. If not specified,
+ an empty string is used. Specifying a label does not improve
+ security.
+
+ :attention: Modify the mask generation function only if you know what you are doing.
+ Sender and receiver must use the same one.
+ """
+ self._key = key
+
+ if hashAlgo:
+ self._hashObj = hashAlgo
+ else:
+ self._hashObj = Crypto.Hash.SHA
+
+ if mgfunc:
+ self._mgf = mgfunc
+ else:
+ self._mgf = lambda x,y: Crypto.Signature.PKCS1_PSS.MGF1(x,y,self._hashObj)
+
+ self._label = label
+
+ def can_encrypt(self):
+ """Return True/1 if this cipher object can be used for encryption."""
+ return self._key.can_encrypt()
+
+ def can_decrypt(self):
+ """Return True/1 if this cipher object can be used for decryption."""
+ return self._key.can_decrypt()
+
+ def encrypt(self, message):
+ """Produce the PKCS#1 OAEP encryption of a message.
+
+ This function is named ``RSAES-OAEP-ENCRYPT``, and is specified in
+ section 7.1.1 of RFC3447.
+
+ :Parameters:
+ message : string
+ The message to encrypt, also known as plaintext. It can be of
+ variable length, but not longer than the RSA modulus (in bytes)
+ minus 2, minus twice the hash output size.
+
+ :Return: A string, the ciphertext in which the message is encrypted.
+ It is as long as the RSA modulus (in bytes).
+ :Raise ValueError:
+ If the RSA key length is not sufficiently long to deal with the given
+ message.
+ """
+ # TODO: Verify the key is RSA
+
+ randFunc = self._key._randfunc
+
+ # See 7.1.1 in RFC3447
+ modBits = Crypto.Util.number.size(self._key.n)
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+ hLen = self._hashObj.digest_size
+ mLen = len(message)
+
+ # Step 1b
+ ps_len = k-mLen-2*hLen-2
+ if ps_len<0:
+ raise ValueError("Plaintext is too long.")
+ # Step 2a
+ lHash = self._hashObj.new(self._label).digest()
+ # Step 2b
+ ps = bchr(0x00)*ps_len
+ # Step 2c
+ db = lHash + ps + bchr(0x01) + message
+ # Step 2d
+ ros = randFunc(hLen)
+ # Step 2e
+ dbMask = self._mgf(ros, k-hLen-1)
+ # Step 2f
+ maskedDB = strxor(db, dbMask)
+ # Step 2g
+ seedMask = self._mgf(maskedDB, hLen)
+ # Step 2h
+ maskedSeed = strxor(ros, seedMask)
+ # Step 2i
+ em = bchr(0x00) + maskedSeed + maskedDB
+ # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP)
+ m = self._key.encrypt(em, 0)[0]
+ # Complete step 3c (I2OSP)
+ c = bchr(0x00)*(k-len(m)) + m
+ return c
+
+ def decrypt(self, ct):
+ """Decrypt a PKCS#1 OAEP ciphertext.
+
+ This function is named ``RSAES-OAEP-DECRYPT``, and is specified in
+ section 7.1.2 of RFC3447.
+
+ :Parameters:
+ ct : string
+ The ciphertext that contains the message to recover.
+
+ :Return: A string, the original message.
+ :Raise ValueError:
+ If the ciphertext length is incorrect, or if the decryption does not
+ succeed.
+ :Raise TypeError:
+ If the RSA key has no private half.
+ """
+ # TODO: Verify the key is RSA
+
+ # See 7.1.2 in RFC3447
+ modBits = Crypto.Util.number.size(self._key.n)
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+ hLen = self._hashObj.digest_size
+
+ # Step 1b and 1c
+ if len(ct) != k or k<hLen+2:
+ raise ValueError("Ciphertext with incorrect length.")
+ # Step 2a (O2SIP), 2b (RSADP), and part of 2c (I2OSP)
+ m = self._key.decrypt(ct)
+ # Complete step 2c (I2OSP)
+ em = bchr(0x00)*(k-len(m)) + m
+ # Step 3a
+ lHash = self._hashObj.new(self._label).digest()
+ # Step 3b
+ y = em[0]
+ # y must be 0, but we MUST NOT check it here in order not to
+ # allow attacks like Manger's (http://dl.acm.org/citation.cfm?id=704143)
+ maskedSeed = em[1:hLen+1]
+ maskedDB = em[hLen+1:]
+ # Step 3c
+ seedMask = self._mgf(maskedDB, hLen)
+ # Step 3d
+ seed = strxor(maskedSeed, seedMask)
+ # Step 3e
+ dbMask = self._mgf(seed, k-hLen-1)
+ # Step 3f
+ db = strxor(maskedDB, dbMask)
+ # Step 3g
+ valid = 1
+ one = db[hLen:].find(bchr(0x01))
+ lHash1 = db[:hLen]
+ if lHash1!=lHash:
+ valid = 0
+ if one<0:
+ valid = 0
+ if bord(y)!=0:
+ valid = 0
+ if not valid:
+ raise ValueError("Incorrect decryption.")
+ # Step 4
+ return db[hLen+one+1:]
+
+def new(key, hashAlgo=None, mgfunc=None, label=b('')):
+ """Return a cipher object `PKCS1OAEP_Cipher` that can be used to perform PKCS#1 OAEP encryption or decryption.
+
+ :Parameters:
+ key : RSA key object
+ The key to use to encrypt or decrypt the message. This is a `Crypto.PublicKey.RSA` object.
+ Decryption is only possible if *key* is a private RSA key.
+ hashAlgo : hash object
+ The hash function to use. This can be a module under `Crypto.Hash`
+ or an existing hash object created from any of such modules. If not specified,
+ `Crypto.Hash.SHA` (that is, SHA-1) is used.
+ mgfunc : callable
+ A mask generation function that accepts two parameters: a string to
+ use as seed, and the lenth of the mask to generate, in bytes.
+ If not specified, the standard MGF1 is used (a safe choice).
+ label : string
+ A label to apply to this particular encryption. If not specified,
+ an empty string is used. Specifying a label does not improve
+ security.
+
+ :attention: Modify the mask generation function only if you know what you are doing.
+ Sender and receiver must use the same one.
+ """
+ return PKCS1OAEP_Cipher(key, hashAlgo, mgfunc, label)
+
diff --git a/lib/Python/Lib/Crypto/Cipher/PKCS1_v1_5.py b/lib/Python/Lib/Crypto/Cipher/PKCS1_v1_5.py
new file mode 100644
index 000000000..c89035d79
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/PKCS1_v1_5.py
@@ -0,0 +1,226 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/PKCS1-v1_5.py : PKCS#1 v1.5
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""RSA encryption protocol according to PKCS#1 v1.5
+
+See RFC3447__ or the `original RSA Labs specification`__ .
+
+This scheme is more properly called ``RSAES-PKCS1-v1_5``.
+
+**If you are designing a new protocol, consider using the more robust PKCS#1 OAEP.**
+
+As an example, a sender may encrypt a message in this way:
+
+ >>> from Crypto.Cipher import PKCS1_v1_5
+ >>> from Crypto.PublicKey import RSA
+ >>> from Crypto.Hash import SHA
+ >>>
+ >>> message = 'To be encrypted'
+ >>> h = SHA.new(message)
+ >>>
+ >>> key = RSA.importKey(open('pubkey.der').read())
+ >>> cipher = PKCS1_v1_5.new(key)
+ >>> ciphertext = cipher.encrypt(message+h.digest())
+
+At the receiver side, decryption can be done using the private part of
+the RSA key:
+
+ >>> From Crypto.Hash import SHA
+ >>> from Crypto import Random
+ >>>
+ >>> key = RSA.importKey(open('privkey.der').read())
+ >>>
+ >>> dsize = SHA.digest_size
+ >>> sentinel = Random.new().read(15+dsize) # Let's assume that average data length is 15
+ >>>
+ >>> cipher = PKCS1_v1_5.new(key)
+ >>> message = cipher.decrypt(ciphertext, sentinel)
+ >>>
+ >>> digest = SHA.new(message[:-dsize]).digest()
+ >>> if digest==message[-dsize:]: # Note how we DO NOT look for the sentinel
+ >>> print "Encryption was correct."
+ >>> else:
+ >>> print "Encryption was not correct."
+
+:undocumented: __revision__, __package__
+
+.. __: http://www.ietf.org/rfc/rfc3447.txt
+.. __: http://www.rsa.com/rsalabs/node.asp?id=2125.
+"""
+
+__revision__ = "$Id$"
+__all__ = [ 'new', 'PKCS115_Cipher' ]
+
+from Crypto.Util.number import ceil_div
+from Crypto.Util.py3compat import *
+import Crypto.Util.number
+
+class PKCS115_Cipher:
+ """This cipher can perform PKCS#1 v1.5 RSA encryption or decryption."""
+
+ def __init__(self, key):
+ """Initialize this PKCS#1 v1.5 cipher object.
+
+ :Parameters:
+ key : an RSA key object
+ If a private half is given, both encryption and decryption are possible.
+ If a public half is given, only encryption is possible.
+ """
+ self._key = key
+
+ def can_encrypt(self):
+ """Return True if this cipher object can be used for encryption."""
+ return self._key.can_encrypt()
+
+ def can_decrypt(self):
+ """Return True if this cipher object can be used for decryption."""
+ return self._key.can_decrypt()
+
+ def encrypt(self, message):
+ """Produce the PKCS#1 v1.5 encryption of a message.
+
+ This function is named ``RSAES-PKCS1-V1_5-ENCRYPT``, and is specified in
+ section 7.2.1 of RFC3447.
+ For a complete example see `Crypto.Cipher.PKCS1_v1_5`.
+
+ :Parameters:
+ message : byte string
+ The message to encrypt, also known as plaintext. It can be of
+ variable length, but not longer than the RSA modulus (in bytes) minus 11.
+
+ :Return: A byte string, the ciphertext in which the message is encrypted.
+ It is as long as the RSA modulus (in bytes).
+ :Raise ValueError:
+ If the RSA key length is not sufficiently long to deal with the given
+ message.
+
+ """
+ # TODO: Verify the key is RSA
+
+ randFunc = self._key._randfunc
+
+ # See 7.2.1 in RFC3447
+ modBits = Crypto.Util.number.size(self._key.n)
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+ mLen = len(message)
+
+ # Step 1
+ if mLen > k-11:
+ raise ValueError("Plaintext is too long.")
+ # Step 2a
+ class nonZeroRandByte:
+ def __init__(self, rf): self.rf=rf
+ def __call__(self, c):
+ while bord(c)==0x00: c=self.rf(1)[0]
+ return c
+ ps = tobytes(map(nonZeroRandByte(randFunc), randFunc(k-mLen-3)))
+ # Step 2b
+ em = b('\x00\x02') + ps + bchr(0x00) + message
+ # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP)
+ m = self._key.encrypt(em, 0)[0]
+ # Complete step 3c (I2OSP)
+ c = bchr(0x00)*(k-len(m)) + m
+ return c
+
+ def decrypt(self, ct, sentinel):
+ """Decrypt a PKCS#1 v1.5 ciphertext.
+
+ This function is named ``RSAES-PKCS1-V1_5-DECRYPT``, and is specified in
+ section 7.2.2 of RFC3447.
+ For a complete example see `Crypto.Cipher.PKCS1_v1_5`.
+
+ :Parameters:
+ ct : byte string
+ The ciphertext that contains the message to recover.
+ sentinel : any type
+ The object to return to indicate that an error was detected during decryption.
+
+ :Return: A byte string. It is either the original message or the ``sentinel`` (in case of an error).
+ :Raise ValueError:
+ If the ciphertext length is incorrect
+ :Raise TypeError:
+ If the RSA key has no private half.
+
+ :attention:
+ You should **never** let the party who submitted the ciphertext know that
+ this function returned the ``sentinel`` value.
+ Armed with such knowledge (for a fair amount of carefully crafted but invalid ciphertexts),
+ an attacker is able to recontruct the plaintext of any other encryption that were carried out
+ with the same RSA public key (see `Bleichenbacher's`__ attack).
+
+ In general, it should not be possible for the other party to distinguish
+ whether processing at the server side failed because the value returned
+ was a ``sentinel`` as opposed to a random, invalid message.
+
+ In fact, the second option is not that unlikely: encryption done according to PKCS#1 v1.5
+ embeds no good integrity check. There is roughly one chance
+ in 2^16 for a random ciphertext to be returned as a valid message
+ (although random looking).
+
+ It is therefore advisabled to:
+
+ 1. Select as ``sentinel`` a value that resembles a plausable random, invalid message.
+ 2. Not report back an error as soon as you detect a ``sentinel`` value.
+ Put differently, you should not explicitly check if the returned value is the ``sentinel`` or not.
+ 3. Cover all possible errors with a single, generic error indicator.
+ 4. Embed into the definition of ``message`` (at the protocol level) a digest (e.g. ``SHA-1``).
+ It is recommended for it to be the rightmost part ``message``.
+ 5. Where possible, monitor the number of errors due to ciphertexts originating from the same party,
+ and slow down the rate of the requests from such party (or even blacklist it altogether).
+
+ **If you are designing a new protocol, consider using the more robust PKCS#1 OAEP.**
+
+ .. __: http://www.bell-labs.com/user/bleichen/papers/pkcs.ps
+
+ """
+
+ # TODO: Verify the key is RSA
+
+ # See 7.2.1 in RFC3447
+ modBits = Crypto.Util.number.size(self._key.n)
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+
+ # Step 1
+ if len(ct) != k:
+ raise ValueError("Ciphertext with incorrect length.")
+ # Step 2a (O2SIP), 2b (RSADP), and part of 2c (I2OSP)
+ m = self._key.decrypt(ct)
+ # Complete step 2c (I2OSP)
+ em = bchr(0x00)*(k-len(m)) + m
+ # Step 3
+ sep = em.find(bchr(0x00),2)
+ if not em.startswith(b('\x00\x02')) or sep<10:
+ return sentinel
+ # Step 4
+ return em[sep+1:]
+
+def new(key):
+ """Return a cipher object `PKCS115_Cipher` that can be used to perform PKCS#1 v1.5 encryption or decryption.
+
+ :Parameters:
+ key : RSA key object
+ The key to use to encrypt or decrypt the message. This is a `Crypto.PublicKey.RSA` object.
+ Decryption is only possible if *key* is a private RSA key.
+
+ """
+ return PKCS115_Cipher(key)
+
diff --git a/lib/Python/Lib/Crypto/Cipher/XOR.py b/lib/Python/Lib/Crypto/Cipher/XOR.py
new file mode 100644
index 000000000..26ec1b108
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/XOR.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/XOR.py : XOR
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""XOR toy cipher
+
+XOR is one the simplest stream ciphers. Encryption and decryption are
+performed by XOR-ing data with a keystream made by contatenating
+the key.
+
+Do not use it for real applications!
+
+:undocumented: __revision__, __package__
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Cipher import _XOR
+
+class XORCipher:
+ """XOR cipher object"""
+
+ def __init__(self, key, *args, **kwargs):
+ """Initialize a XOR cipher object
+
+ See also `new()` at the module level."""
+ self._cipher = _XOR.new(key, *args, **kwargs)
+ self.block_size = self._cipher.block_size
+ self.key_size = self._cipher.key_size
+
+ def encrypt(self, plaintext):
+ """Encrypt a piece of data.
+
+ :Parameters:
+ plaintext : byte string
+ The piece of data to encrypt. It can be of any size.
+ :Return: the encrypted data (byte string, as long as the
+ plaintext).
+ """
+ return self._cipher.encrypt(plaintext)
+
+ def decrypt(self, ciphertext):
+ """Decrypt a piece of data.
+
+ :Parameters:
+ ciphertext : byte string
+ The piece of data to decrypt. It can be of any size.
+ :Return: the decrypted data (byte string, as long as the
+ ciphertext).
+ """
+ return self._cipher.decrypt(ciphertext)
+
+def new(key, *args, **kwargs):
+ """Create a new XOR cipher
+
+ :Parameters:
+ key : byte string
+ The secret key to use in the symmetric cipher.
+ Its length may vary from 1 to 32 bytes.
+
+ :Return: an `XORCipher` object
+ """
+ return XORCipher(key, *args, **kwargs)
+
+#: Size of a data block (in bytes)
+block_size = 1
+#: Size of a key (in bytes)
+key_size = xrange(1,32+1)
+
diff --git a/lib/Python/Lib/Crypto/Cipher/__init__.py b/lib/Python/Lib/Crypto/Cipher/__init__.py
new file mode 100644
index 000000000..7afed2d4b
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/__init__.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Symmetric- and asymmetric-key encryption algorithms.
+
+Encryption algorithms transform plaintext in some way that
+is dependent on a key or key pair, producing ciphertext.
+
+Symmetric algorithms
+--------------------
+
+Encryption can easily be reversed, if (and, hopefully, only if)
+one knows the same key.
+In other words, sender and receiver share the same key.
+
+The symmetric encryption modules here all support the interface described in PEP
+272, "API for Block Encryption Algorithms".
+
+If you don't know which algorithm to choose, use AES because it's
+standard and has undergone a fair bit of examination.
+
+======================== ======= ========================
+Module name Type Description
+======================== ======= ========================
+`Crypto.Cipher.AES` Block Advanced Encryption Standard
+`Crypto.Cipher.ARC2` Block Alleged RC2
+`Crypto.Cipher.ARC4` Stream Alleged RC4
+`Crypto.Cipher.Blowfish` Block Blowfish
+`Crypto.Cipher.CAST` Block CAST
+`Crypto.Cipher.DES` Block The Data Encryption Standard.
+ Very commonly used in the past,
+ but today its 56-bit keys are too small.
+`Crypto.Cipher.DES3` Block Triple DES.
+`Crypto.Cipher.XOR` Stream The simple XOR cipher.
+======================== ======= ========================
+
+
+Asymmetric algorithms
+---------------------
+
+For asymmetric algorithms, the key to be used for decryption is totally
+different and cannot be derived in a feasible way from the key used
+for encryption. Put differently, sender and receiver each own one half
+of a key pair. The encryption key is often called ``public`` whereas
+the decryption key is called ``private``.
+
+========================== =======================
+Module name Description
+========================== =======================
+`Crypto.Cipher.PKCS1_v1_5` PKCS#1 v1.5 encryption, based on RSA key pairs
+`Crypto.Cipher.PKCS1_OAEP` PKCS#1 OAEP encryption, based on RSA key pairs
+========================== =======================
+
+:undocumented: __revision__, __package__, _AES, _ARC2, _ARC4, _Blowfish
+ _CAST, _DES, _DES3, _XOR
+"""
+
+__all__ = ['AES', 'ARC2', 'ARC4',
+ 'Blowfish', 'CAST', 'DES', 'DES3',
+ 'XOR',
+ 'PKCS1_v1_5', 'PKCS1_OAEP'
+ ]
+
+__revision__ = "$Id$"
+
+
diff --git a/lib/Python/Lib/Crypto/Cipher/blockalgo.py b/lib/Python/Lib/Crypto/Cipher/blockalgo.py
new file mode 100644
index 000000000..dd183dcb5
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Cipher/blockalgo.py
@@ -0,0 +1,296 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/blockalgo.py
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""Module with definitions common to all block ciphers."""
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+#: *Electronic Code Book (ECB)*.
+#: This is the simplest encryption mode. Each of the plaintext blocks
+#: is directly encrypted into a ciphertext block, independently of
+#: any other block. This mode exposes frequency of symbols
+#: in your plaintext. Other modes (e.g. *CBC*) should be used instead.
+#:
+#: See `NIST SP800-38A`_ , Section 6.1 .
+#:
+#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
+MODE_ECB = 1
+
+#: *Cipher-Block Chaining (CBC)*. Each of the ciphertext blocks depends
+#: on the current and all previous plaintext blocks. An Initialization Vector
+#: (*IV*) is required.
+#:
+#: The *IV* is a data block to be transmitted to the receiver.
+#: The *IV* can be made public, but it must be authenticated by the receiver and
+#: it should be picked randomly.
+#:
+#: See `NIST SP800-38A`_ , Section 6.2 .
+#:
+#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
+MODE_CBC = 2
+
+#: *Cipher FeedBack (CFB)*. This mode is similar to CBC, but it transforms
+#: the underlying block cipher into a stream cipher. Plaintext and ciphertext
+#: are processed in *segments* of **s** bits. The mode is therefore sometimes
+#: labelled **s**-bit CFB. An Initialization Vector (*IV*) is required.
+#:
+#: When encrypting, each ciphertext segment contributes to the encryption of
+#: the next plaintext segment.
+#:
+#: This *IV* is a data block to be transmitted to the receiver.
+#: The *IV* can be made public, but it should be picked randomly.
+#: Reusing the same *IV* for encryptions done with the same key lead to
+#: catastrophic cryptographic failures.
+#:
+#: See `NIST SP800-38A`_ , Section 6.3 .
+#:
+#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
+MODE_CFB = 3
+
+#: This mode should not be used.
+MODE_PGP = 4
+
+#: *Output FeedBack (OFB)*. This mode is very similar to CBC, but it
+#: transforms the underlying block cipher into a stream cipher.
+#: The keystream is the iterated block encryption of an Initialization Vector (*IV*).
+#:
+#: The *IV* is a data block to be transmitted to the receiver.
+#: The *IV* can be made public, but it should be picked randomly.
+#:
+#: Reusing the same *IV* for encryptions done with the same key lead to
+#: catastrophic cryptograhic failures.
+#:
+#: See `NIST SP800-38A`_ , Section 6.4 .
+#:
+#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
+MODE_OFB = 5
+
+#: *CounTeR (CTR)*. This mode is very similar to ECB, in that
+#: encryption of one block is done independently of all other blocks.
+#: Unlike ECB, the block *position* contributes to the encryption and no
+#: information leaks about symbol frequency.
+#:
+#: Each message block is associated to a *counter* which must be unique
+#: across all messages that get encrypted with the same key (not just within
+#: the same message). The counter is as big as the block size.
+#:
+#: Counters can be generated in several ways. The most straightword one is
+#: to choose an *initial counter block* (which can be made public, similarly
+#: to the *IV* for the other modes) and increment its lowest **m** bits by
+#: one (modulo *2^m*) for each block. In most cases, **m** is chosen to be half
+#: the block size.
+#:
+#: Reusing the same *initial counter block* for encryptions done with the same
+#: key lead to catastrophic cryptograhic failures.
+#:
+#: See `NIST SP800-38A`_ , Section 6.5 (for the mode) and Appendix B (for how
+#: to manage the *initial counter block*).
+#:
+#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
+MODE_CTR = 6
+
+#: OpenPGP. This mode is a variant of CFB, and it is only used in PGP and OpenPGP_ applications.
+#: An Initialization Vector (*IV*) is required.
+#:
+#: Unlike CFB, the IV is not transmitted to the receiver. Instead, the *encrypted* IV is.
+#: The IV is a random data block. Two of its bytes are duplicated to act as a checksum
+#: for the correctness of the key. The encrypted IV is therefore 2 bytes longer than
+#: the clean IV.
+#:
+#: .. _OpenPGP: http://tools.ietf.org/html/rfc4880
+MODE_OPENPGP = 7
+
+def _getParameter(name, index, args, kwargs, default=None):
+ """Find a parameter in tuple and dictionary arguments a function receives"""
+ param = kwargs.get(name)
+ if len(args)>index:
+ if param:
+ raise ValueError("Parameter '%s' is specified twice" % name)
+ param = args[index]
+ return param or default
+
+class BlockAlgo:
+ """Class modelling an abstract block cipher."""
+
+ def __init__(self, factory, key, *args, **kwargs):
+ self.mode = _getParameter('mode', 0, args, kwargs, default=MODE_ECB)
+ self.block_size = factory.block_size
+
+ if self.mode != MODE_OPENPGP:
+ self._cipher = factory.new(key, *args, **kwargs)
+ self.IV = self._cipher.IV
+ else:
+ # OPENPGP mode. For details, see 13.9 in RCC4880.
+ #
+ # A few members are specifically created for this mode:
+ # - _encrypted_iv, set in this constructor
+ # - _done_first_block, set to True after the first encryption
+ # - _done_last_block, set to True after a partial block is processed
+
+ self._done_first_block = False
+ self._done_last_block = False
+ self.IV = _getParameter('iv', 1, args, kwargs)
+ if not self.IV:
+ raise ValueError("MODE_OPENPGP requires an IV")
+
+ # Instantiate a temporary cipher to process the IV
+ IV_cipher = factory.new(key, MODE_CFB,
+ b('\x00')*self.block_size, # IV for CFB
+ segment_size=self.block_size*8)
+
+ # The cipher will be used for...
+ if len(self.IV) == self.block_size:
+ # ... encryption
+ self._encrypted_IV = IV_cipher.encrypt(
+ self.IV + self.IV[-2:] + # Plaintext
+ b('\x00')*(self.block_size-2) # Padding
+ )[:self.block_size+2]
+ elif len(self.IV) == self.block_size+2:
+ # ... decryption
+ self._encrypted_IV = self.IV
+ self.IV = IV_cipher.decrypt(self.IV + # Ciphertext
+ b('\x00')*(self.block_size-2) # Padding
+ )[:self.block_size+2]
+ if self.IV[-2:] != self.IV[-4:-2]:
+ raise ValueError("Failed integrity check for OPENPGP IV")
+ self.IV = self.IV[:-2]
+ else:
+ raise ValueError("Length of IV must be %d or %d bytes for MODE_OPENPGP"
+ % (self.block_size, self.block_size+2))
+
+ # Instantiate the cipher for the real PGP data
+ self._cipher = factory.new(key, MODE_CFB,
+ self._encrypted_IV[-self.block_size:],
+ segment_size=self.block_size*8)
+
+ def encrypt(self, plaintext):
+ """Encrypt data with the key and the parameters set at initialization.
+
+ The cipher object is stateful; encryption of a long block
+ of data can be broken up in two or more calls to `encrypt()`.
+ That is, the statement:
+
+ >>> c.encrypt(a) + c.encrypt(b)
+
+ is always equivalent to:
+
+ >>> c.encrypt(a+b)
+
+ That also means that you cannot reuse an object for encrypting
+ or decrypting other data with the same key.
+
+ This function does not perform any padding.
+
+ - For `MODE_ECB`, `MODE_CBC`, and `MODE_OFB`, *plaintext* length
+ (in bytes) must be a multiple of *block_size*.
+
+ - For `MODE_CFB`, *plaintext* length (in bytes) must be a multiple
+ of *segment_size*/8.
+
+ - For `MODE_CTR`, *plaintext* can be of any length.
+
+ - For `MODE_OPENPGP`, *plaintext* must be a multiple of *block_size*,
+ unless it is the last chunk of the message.
+
+ :Parameters:
+ plaintext : byte string
+ The piece of data to encrypt.
+ :Return:
+ the encrypted data, as a byte string. It is as long as
+ *plaintext* with one exception: when encrypting the first message
+ chunk with `MODE_OPENPGP`, the encypted IV is prepended to the
+ returned ciphertext.
+ """
+
+ if self.mode == MODE_OPENPGP:
+ padding_length = (self.block_size - len(plaintext) % self.block_size) % self.block_size
+ if padding_length>0:
+ # CFB mode requires ciphertext to have length multiple of block size,
+ # but PGP mode allows the last block to be shorter
+ if self._done_last_block:
+ raise ValueError("Only the last chunk is allowed to have length not multiple of %d bytes",
+ self.block_size)
+ self._done_last_block = True
+ padded = plaintext + b('\x00')*padding_length
+ res = self._cipher.encrypt(padded)[:len(plaintext)]
+ else:
+ res = self._cipher.encrypt(plaintext)
+ if not self._done_first_block:
+ res = self._encrypted_IV + res
+ self._done_first_block = True
+ return res
+
+ return self._cipher.encrypt(plaintext)
+
+ def decrypt(self, ciphertext):
+ """Decrypt data with the key and the parameters set at initialization.
+
+ The cipher object is stateful; decryption of a long block
+ of data can be broken up in two or more calls to `decrypt()`.
+ That is, the statement:
+
+ >>> c.decrypt(a) + c.decrypt(b)
+
+ is always equivalent to:
+
+ >>> c.decrypt(a+b)
+
+ That also means that you cannot reuse an object for encrypting
+ or decrypting other data with the same key.
+
+ This function does not perform any padding.
+
+ - For `MODE_ECB`, `MODE_CBC`, and `MODE_OFB`, *ciphertext* length
+ (in bytes) must be a multiple of *block_size*.
+
+ - For `MODE_CFB`, *ciphertext* length (in bytes) must be a multiple
+ of *segment_size*/8.
+
+ - For `MODE_CTR`, *ciphertext* can be of any length.
+
+ - For `MODE_OPENPGP`, *plaintext* must be a multiple of *block_size*,
+ unless it is the last chunk of the message.
+
+ :Parameters:
+ ciphertext : byte string
+ The piece of data to decrypt.
+ :Return: the decrypted data (byte string, as long as *ciphertext*).
+ """
+ if self.mode == MODE_OPENPGP:
+ padding_length = (self.block_size - len(ciphertext) % self.block_size) % self.block_size
+ if padding_length>0:
+ # CFB mode requires ciphertext to have length multiple of block size,
+ # but PGP mode allows the last block to be shorter
+ if self._done_last_block:
+ raise ValueError("Only the last chunk is allowed to have length not multiple of %d bytes",
+ self.block_size)
+ self._done_last_block = True
+ padded = ciphertext + b('\x00')*padding_length
+ res = self._cipher.decrypt(padded)[:len(ciphertext)]
+ else:
+ res = self._cipher.decrypt(ciphertext)
+ return res
+
+ return self._cipher.decrypt(ciphertext)
+
diff --git a/lib/Python/Lib/Crypto/Hash/HMAC.py b/lib/Python/Lib/Crypto/Hash/HMAC.py
new file mode 100644
index 000000000..6244db46f
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/HMAC.py
@@ -0,0 +1,212 @@
+# HMAC.py - Implements the HMAC algorithm as described by RFC 2104.
+#
+# ===================================================================
+# Portions Copyright (c) 2001, 2002, 2003 Python Software Foundation;
+# All Rights Reserved
+#
+# This file contains code from the Python 2.2 hmac.py module (the
+# "Original Code"), with modifications made after it was incorporated
+# into PyCrypto (the "Modifications").
+#
+# To the best of our knowledge, the Python Software Foundation is the
+# copyright holder of the Original Code, and has licensed it under the
+# Python 2.2 license. See the file LEGAL/copy/LICENSE.python-2.2 for
+# details.
+#
+# The Modifications to this file are dedicated to the public domain.
+# To the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever. No rights are
+# reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+
+"""HMAC (Hash-based Message Authentication Code) algorithm
+
+HMAC is a MAC defined in RFC2104_ and FIPS-198_ and constructed using
+a cryptograpic hash algorithm.
+It is usually named *HMAC-X*, where *X* is the hash algorithm; for
+instance *HMAC-SHA1* or *HMAC-MD5*.
+
+The strength of an HMAC depends on:
+
+ - the strength of the hash algorithm
+ - the length and entropy of the secret key
+
+An example of possible usage is the following:
+
+ >>> from Crypto.Hash import HMAC
+ >>>
+ >>> secret = b'Swordfish'
+ >>> h = HMAC.new(secret)
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+.. _RFC2104: http://www.ietf.org/rfc/rfc2104.txt
+.. _FIPS-198: http://csrc.nist.gov/publications/fips/fips198/fips-198a.pdf
+"""
+
+# This is just a copy of the Python 2.2 HMAC module, modified to work when
+# used on versions of Python before 2.2.
+
+__revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'HMAC' ]
+
+from Crypto.Util.strxor import strxor_c
+from Crypto.Util.py3compat import *
+
+#: The size of the authentication tag produced by the MAC.
+#: It matches the digest size on the underlying
+#: hashing module used.
+digest_size = None
+
+class HMAC:
+ """Class that implements HMAC"""
+
+ #: The size of the authentication tag produced by the MAC.
+ #: It matches the digest size on the underlying
+ #: hashing module used.
+ digest_size = None
+
+ def __init__(self, key, msg = None, digestmod = None):
+ """Create a new HMAC object.
+
+ :Parameters:
+ key : byte string
+ secret key for the MAC object.
+ It must be long enough to match the expected security level of the
+ MAC. However, there is no benefit in using keys longer than the
+ `digest_size` of the underlying hash algorithm.
+ msg : byte string
+ The very first chunk of the message to authenticate.
+ It is equivalent to an early call to `update()`. Optional.
+ :Parameter digestmod:
+ The hash algorithm the HMAC is based on.
+ Default is `Crypto.Hash.MD5`.
+ :Type digestmod:
+ A hash module or object instantiated from `Crypto.Hash`
+ """
+ if digestmod is None:
+ import MD5
+ digestmod = MD5
+
+ self.digestmod = digestmod
+ self.outer = digestmod.new()
+ self.inner = digestmod.new()
+ try:
+ self.digest_size = digestmod.digest_size
+ except AttributeError:
+ self.digest_size = len(self.outer.digest())
+
+ try:
+ # The block size is 128 bytes for SHA384 and SHA512 and 64 bytes
+ # for the others hash function
+ blocksize = digestmod.block_size
+ except AttributeError:
+ blocksize = 64
+
+ ipad = 0x36
+ opad = 0x5C
+
+ if len(key) > blocksize:
+ key = digestmod.new(key).digest()
+
+ key = key + bchr(0) * (blocksize - len(key))
+ self.outer.update(strxor_c(key, opad))
+ self.inner.update(strxor_c(key, ipad))
+ if (msg):
+ self.update(msg)
+
+ def update(self, msg):
+ """Continue authentication of a message by consuming the next chunk of data.
+
+ Repeated calls are equivalent to a single call with the concatenation
+ of all the arguments. In other words:
+
+ >>> m.update(a); m.update(b)
+
+ is equivalent to:
+
+ >>> m.update(a+b)
+
+ :Parameters:
+ msg : byte string
+ The next chunk of the message being authenticated
+ """
+
+ self.inner.update(msg)
+
+ def copy(self):
+ """Return a copy ("clone") of the MAC object.
+
+ The copy will have the same internal state as the original MAC
+ object.
+ This can be used to efficiently compute the MAC of strings that
+ share a common initial substring.
+
+ :Returns: An `HMAC` object
+ """
+ other = HMAC(b(""))
+ other.digestmod = self.digestmod
+ other.inner = self.inner.copy()
+ other.outer = self.outer.copy()
+ return other
+
+ def digest(self):
+ """Return the **binary** (non-printable) MAC of the message that has
+ been authenticated so far.
+
+ This method does not change the state of the MAC object.
+ You can continue updating the object after calling this function.
+
+ :Return: A byte string of `digest_size` bytes. It may contain non-ASCII
+ characters, including null bytes.
+ """
+ h = self.outer.copy()
+ h.update(self.inner.digest())
+ return h.digest()
+
+ def hexdigest(self):
+ """Return the **printable** MAC of the message that has been
+ authenticated so far.
+
+ This method does not change the state of the MAC object.
+
+ :Return: A string of 2* `digest_size` bytes. It contains only
+ hexadecimal ASCII digits.
+ """
+ return "".join(["%02x" % bord(x)
+ for x in tuple(self.digest())])
+
+def new(key, msg = None, digestmod = None):
+ """Create a new HMAC object.
+
+ :Parameters:
+ key : byte string
+ key for the MAC object.
+ It must be long enough to match the expected security level of the
+ MAC. However, there is no benefit in using keys longer than the
+ `digest_size` of the underlying hash algorithm.
+ msg : byte string
+ The very first chunk of the message to authenticate.
+ It is equivalent to an early call to `HMAC.update()`.
+ Optional.
+ :Parameter digestmod:
+ The hash to use to implement the HMAC. Default is `Crypto.Hash.MD5`.
+ :Type digestmod:
+ A hash module or instantiated object from `Crypto.Hash`
+ :Returns: An `HMAC` object
+ """
+ return HMAC(key, msg, digestmod)
+
diff --git a/lib/Python/Lib/Crypto/Hash/MD2.py b/lib/Python/Lib/Crypto/Hash/MD2.py
new file mode 100644
index 000000000..dac959e44
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/MD2.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""MD2 cryptographic hash algorithm.
+
+MD2 is specified in RFC1319_ and it produces the 128 bit digest of a message.
+
+ >>> from Crypto.Hash import MD2
+ >>>
+ >>> h = MD2.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+MD2 stand for Message Digest version 2, and it was invented by Rivest in 1989.
+
+This algorithm is both slow and insecure. Do not use it for new designs.
+
+.. _RFC1319: http://tools.ietf.org/html/rfc1319
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'MD2Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+import Crypto.Hash._MD2 as _MD2
+hashFactory = _MD2
+
+class MD2Hash(HashAlgo):
+ """Class that implements an MD2 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-md2 OBJECT IDENTIFIER ::= {
+ #: iso(1) member-body(2) us(840) rsadsi(113549)
+ #: digestAlgorithm(2) 2
+ #: }
+ #:
+ #: This value uniquely identifies the MD2 algorithm.
+ oid = b('\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02')
+
+ digest_size = 16
+ block_size = 16
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return MD2Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `MD2Hash.update()`.
+ Optional.
+
+ :Return: An `MD2Hash` object
+ """
+ return MD2Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = MD2Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = MD2Hash.block_size
+
diff --git a/lib/Python/Lib/Crypto/Hash/MD4.py b/lib/Python/Lib/Crypto/Hash/MD4.py
new file mode 100644
index 000000000..e28a2011d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/MD4.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""MD4 cryptographic hash algorithm.
+
+MD4 is specified in RFC1320_ and produces the 128 bit digest of a message.
+
+ >>> from Crypto.Hash import MD4
+ >>>
+ >>> h = MD4.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+MD4 stand for Message Digest version 4, and it was invented by Rivest in 1990.
+
+This algorithm is insecure. Do not use it for new designs.
+
+.. _RFC1320: http://tools.ietf.org/html/rfc1320
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'MD4Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+import Crypto.Hash._MD4 as _MD4
+hashFactory = _MD4
+
+class MD4Hash(HashAlgo):
+ """Class that implements an MD4 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-md2 OBJECT IDENTIFIER ::= {
+ #: iso(1) member-body(2) us(840) rsadsi(113549)
+ #: digestAlgorithm(2) 4
+ #: }
+ #:
+ #: This value uniquely identifies the MD4 algorithm.
+ oid = b('\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x04')
+
+ digest_size = 16
+ block_size = 64
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return MD4Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `MD4Hash.update()`.
+ Optional.
+
+ :Return: A `MD4Hash` object
+ """
+ return MD4Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = MD4Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = MD4Hash.block_size
+
diff --git a/lib/Python/Lib/Crypto/Hash/MD5.py b/lib/Python/Lib/Crypto/Hash/MD5.py
new file mode 100644
index 000000000..18e9e7bf6
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/MD5.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""MD5 cryptographic hash algorithm.
+
+MD5 is specified in RFC1321_ and produces the 128 bit digest of a message.
+
+ >>> from Crypto.Hash import MD5
+ >>>
+ >>> h = MD5.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+MD5 stand for Message Digest version 5, and it was invented by Rivest in 1991.
+
+This algorithm is insecure. Do not use it for new designs.
+
+.. _RFC1321: http://tools.ietf.org/html/rfc1321
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'MD5Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+try:
+ # The md5 module is deprecated in Python 2.6, so use hashlib when possible.
+ import hashlib
+ hashFactory = hashlib.md5
+
+except ImportError:
+ import md5
+ hashFactory = md5
+
+class MD5Hash(HashAlgo):
+ """Class that implements an MD5 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-md5 OBJECT IDENTIFIER ::= {
+ #: iso(1) member-body(2) us(840) rsadsi(113549)
+ #: digestAlgorithm(2) 5
+ #: }
+ #:
+ #: This value uniquely identifies the MD5 algorithm.
+ oid = b('\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05')
+
+ digest_size = 16
+ block_size = 64
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return MD5Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `MD5Hash.update()`.
+ Optional.
+
+ :Return: A `MD5Hash` object
+ """
+ return MD5Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = MD5Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = MD5Hash.block_size
+
diff --git a/lib/Python/Lib/Crypto/Hash/RIPEMD.py b/lib/Python/Lib/Crypto/Hash/RIPEMD.py
new file mode 100644
index 000000000..33099cb8c
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/RIPEMD.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""RIPEMD-160 cryptographic hash algorithm.
+
+RIPEMD-160_ produces the 160 bit digest of a message.
+
+ >>> from Crypto.Hash import RIPEMD
+ >>>
+ >>> h = RIPEMD.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+RIPEMD-160 stands for RACE Integrity Primitives Evaluation Message Digest
+with a 160 bit digest. It was invented by Dobbertin, Bosselaers, and Preneel.
+
+This algorithm is considered secure, although it has not been scrutinized as
+extensively as SHA-1. Moreover, it provides an informal security level of just
+80bits.
+
+.. _RIPEMD-160: http://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'RIPEMD160Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+import Crypto.Hash._RIPEMD160 as _RIPEMD160
+hashFactory = _RIPEMD160
+
+class RIPEMD160Hash(HashAlgo):
+ """Class that implements a RIPMD-160 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-ripemd160 OBJECT IDENTIFIER ::= {
+ #: iso(1) identified-organization(3) teletrust(36)
+ #: algorithm(3) hashAlgorithm(2) ripemd160(1)
+ #: }
+ #:
+ #: This value uniquely identifies the RIPMD-160 algorithm.
+ oid = b("\x06\x05\x2b\x24\x03\x02\x01")
+
+ digest_size = 20
+ block_size = 64
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return RIPEMD160Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `RIPEMD160Hash.update()`.
+ Optional.
+
+ :Return: A `RIPEMD160Hash` object
+ """
+ return RIPEMD160Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = RIPEMD160Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = RIPEMD160Hash.block_size
+
diff --git a/lib/Python/Lib/Crypto/Hash/SHA.py b/lib/Python/Lib/Crypto/Hash/SHA.py
new file mode 100644
index 000000000..0bc591705
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/SHA.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""SHA-1 cryptographic hash algorithm.
+
+SHA-1_ produces the 160 bit digest of a message.
+
+ >>> from Crypto.Hash import SHA
+ >>>
+ >>> h = SHA.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+*SHA* stands for Secure Hash Algorithm.
+
+This algorithm is not considered secure. Do not use it for new designs.
+
+.. _SHA-1: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'SHA1Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+try:
+ # The sha module is deprecated in Python 2.6, so use hashlib when possible.
+ import hashlib
+ hashFactory = hashlib.sha1
+
+except ImportError:
+ import sha
+ hashFactory = sha
+
+class SHA1Hash(HashAlgo):
+ """Class that implements a SHA-1 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-sha1 OBJECT IDENTIFIER ::= {
+ #: iso(1) identified-organization(3) oiw(14) secsig(3)
+ #: algorithms(2) 26
+ #: }
+ #:
+ #: This value uniquely identifies the SHA-1 algorithm.
+ oid = b('\x06\x05\x2b\x0e\x03\x02\x1a')
+
+ digest_size = 20
+ block_size = 64
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return SHA1Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `SHA1Hash.update()`.
+ Optional.
+
+ :Return: A `SHA1Hash` object
+ """
+ return SHA1Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = SHA1Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = SHA1Hash.block_size
+
+
diff --git a/lib/Python/Lib/Crypto/Hash/SHA224.py b/lib/Python/Lib/Crypto/Hash/SHA224.py
new file mode 100644
index 000000000..959b56daf
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/SHA224.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""SHA-224 cryptographic hash algorithm.
+
+SHA-224 belongs to the SHA-2_ family of cryptographic hashes.
+It produces the 224 bit digest of a message.
+
+ >>> from Crypto.Hash import SHA224
+ >>>
+ >>> h = SHA224.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+*SHA* stands for Secure Hash Algorithm.
+
+.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'SHA224Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+try:
+ import hashlib
+ hashFactory = hashlib.sha224
+
+except ImportError:
+ from Crypto.Hash import _SHA224
+ hashFactory = _SHA224
+
+class SHA224Hash(HashAlgo):
+ """Class that implements a SHA-224 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-sha224 OBJECT IDENTIFIER ::= {
+ #: joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3)
+ #: nistalgorithm(4) hashalgs(2) 4
+ #: }
+ #:
+ #: This value uniquely identifies the SHA-224 algorithm.
+ oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04')
+
+ digest_size = 28
+ block_size = 64
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return SHA224Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `SHA224Hash.update()`.
+ Optional.
+
+ :Return: A `SHA224Hash` object
+ """
+ return SHA224Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = SHA224Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = SHA224Hash.block_size
+
diff --git a/lib/Python/Lib/Crypto/Hash/SHA256.py b/lib/Python/Lib/Crypto/Hash/SHA256.py
new file mode 100644
index 000000000..b0a99b31a
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/SHA256.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""SHA-256 cryptographic hash algorithm.
+
+SHA-256 belongs to the SHA-2_ family of cryptographic hashes.
+It produces the 256 bit digest of a message.
+
+ >>> from Crypto.Hash import SHA256
+ >>>
+ >>> h = SHA256.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+*SHA* stands for Secure Hash Algorithm.
+
+.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'SHA256Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+try:
+ import hashlib
+ hashFactory = hashlib.sha256
+
+except ImportError:
+ from Crypto.Hash import _SHA256
+ hashFactory = _SHA256
+
+class SHA256Hash(HashAlgo):
+ """Class that implements a SHA-256 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-sha256 OBJECT IDENTIFIER ::= {
+ #: joint-iso-itu-t(2) country(16) us(840) organization(1)
+ #: gov(101) csor(3) nistalgorithm(4) hashalgs(2) 1
+ #: }
+ #:
+ #: This value uniquely identifies the SHA-256 algorithm.
+ oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01')
+
+ digest_size = 32
+ block_size = 64
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return SHA256Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `SHA256Hash.update()`.
+ Optional.
+
+ :Return: A `SHA256Hash` object
+ """
+ return SHA256Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = SHA256Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = SHA256Hash.block_size
+
diff --git a/lib/Python/Lib/Crypto/Hash/SHA384.py b/lib/Python/Lib/Crypto/Hash/SHA384.py
new file mode 100644
index 000000000..3490b021e
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/SHA384.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""SHA-384 cryptographic hash algorithm.
+
+SHA-384 belongs to the SHA-2_ family of cryptographic hashes.
+It produces the 384 bit digest of a message.
+
+ >>> from Crypto.Hash import SHA384
+ >>>
+ >>> h = SHA384.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+*SHA* stands for Secure Hash Algorithm.
+
+.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'SHA384Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+try:
+ import hashlib
+ hashFactory = hashlib.sha384
+
+except ImportError:
+ from Crypto.Hash import _SHA384
+ hashFactory = _SHA384
+
+class SHA384Hash(HashAlgo):
+ """Class that implements a SHA-384 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-sha384 OBJECT IDENTIFIER ::= {
+ #: joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3)
+ #: nistalgorithm(4) hashalgs(2) 2
+ #: }
+ #:
+ #: This value uniquely identifies the SHA-384 algorithm.
+ oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02')
+
+ digest_size = 48
+ block_size = 128
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return SHA384Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `SHA384Hash.update()`.
+ Optional.
+
+ :Return: A `SHA384Hash` object
+ """
+ return SHA384Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = SHA384Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = SHA384Hash.block_size
+
+
diff --git a/lib/Python/Lib/Crypto/Hash/SHA512.py b/lib/Python/Lib/Crypto/Hash/SHA512.py
new file mode 100644
index 000000000..d57548d35
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/SHA512.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""SHA-512 cryptographic hash algorithm.
+
+SHA-512 belongs to the SHA-2_ family of cryptographic hashes.
+It produces the 512 bit digest of a message.
+
+ >>> from Crypto.Hash import SHA512
+ >>>
+ >>> h = SHA512.new()
+ >>> h.update(b'Hello')
+ >>> print h.hexdigest()
+
+*SHA* stands for Secure Hash Algorithm.
+
+.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
+"""
+
+_revision__ = "$Id$"
+
+__all__ = ['new', 'digest_size', 'SHA512Hash' ]
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash.hashalgo import HashAlgo
+
+try:
+ import hashlib
+ hashFactory = hashlib.sha512
+
+except ImportError:
+ from Crypto.Hash import _SHA512
+ hashFactory = _SHA512
+
+class SHA512Hash(HashAlgo):
+ """Class that implements a SHA-512 hash
+
+ :undocumented: block_size
+ """
+
+ #: ASN.1 Object identifier (OID)::
+ #:
+ #: id-sha512 OBJECT IDENTIFIER ::= {
+ #: joint-iso-itu-t(2)
+ #: country(16) us(840) organization(1) gov(101) csor(3) nistalgorithm(4) hashalgs(2) 3
+ #: }
+ #:
+ #: This value uniquely identifies the SHA-512 algorithm.
+ oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03')
+
+ digest_size = 64
+ block_size = 128
+
+ def __init__(self, data=None):
+ HashAlgo.__init__(self, hashFactory, data)
+
+ def new(self, data=None):
+ return SHA512Hash(data)
+
+def new(data=None):
+ """Return a fresh instance of the hash object.
+
+ :Parameters:
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `SHA512Hash.update()`.
+ Optional.
+
+ :Return: A `SHA512Hash` object
+ """
+ return SHA512Hash().new(data)
+
+#: The size of the resulting hash in bytes.
+digest_size = SHA512Hash.digest_size
+
+#: The internal block size of the hash algorithm in bytes.
+block_size = SHA512Hash.block_size
+
diff --git a/lib/Python/Lib/Crypto/Hash/__init__.py b/lib/Python/Lib/Crypto/Hash/__init__.py
new file mode 100644
index 000000000..4582c663d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/__init__.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Hashing algorithms
+
+Hash functions take arbitrary binary strings as input, and produce a random-like output
+of fixed size that is dependent on the input; it should be practically infeasible
+to derive the original input data given only the hash function's
+output. In other words, the hash function is *one-way*.
+
+It should also not be practically feasible to find a second piece of data
+(a *second pre-image*) whose hash is the same as the original message
+(*weak collision resistance*).
+
+Finally, it should not be feasible to find two arbitrary messages with the
+same hash (*strong collision resistance*).
+
+The output of the hash function is called the *digest* of the input message.
+In general, the security of a hash function is related to the length of the
+digest. If the digest is *n* bits long, its security level is roughly comparable
+to the the one offered by an *n/2* bit encryption algorithm.
+
+Hash functions can be used simply as a integrity check, or, in
+association with a public-key algorithm, can be used to implement
+digital signatures.
+
+The hashing modules here all support the interface described in `PEP
+247`_ , "API for Cryptographic Hash Functions".
+
+.. _`PEP 247` : http://www.python.org/dev/peps/pep-0247/
+
+:undocumented: _MD2, _MD4, _RIPEMD160, _SHA224, _SHA256, _SHA384, _SHA512
+"""
+
+__all__ = ['HMAC', 'MD2', 'MD4', 'MD5', 'RIPEMD', 'SHA',
+ 'SHA224', 'SHA256', 'SHA384', 'SHA512']
+__revision__ = "$Id$"
+
+
diff --git a/lib/Python/Lib/Crypto/Hash/hashalgo.py b/lib/Python/Lib/Crypto/Hash/hashalgo.py
new file mode 100644
index 000000000..b38b3a6c1
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Hash/hashalgo.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+from binascii import hexlify
+
+class HashAlgo:
+ """A generic class for an abstract cryptographic hash algorithm.
+
+ :undocumented: block_size
+ """
+
+ #: The size of the resulting hash in bytes.
+ digest_size = None
+ #: The internal block size of the hash algorithm in bytes.
+ block_size = None
+
+ def __init__(self, hashFactory, data=None):
+ """Initialize the hash object.
+
+ :Parameters:
+ hashFactory : callable
+ An object that will generate the actual hash implementation.
+ *hashFactory* must have a *new()* method, or must be directly
+ callable.
+ data : byte string
+ The very first chunk of the message to hash.
+ It is equivalent to an early call to `update()`.
+ """
+ if hasattr(hashFactory, 'new'):
+ self._hash = hashFactory.new()
+ else:
+ self._hash = hashFactory()
+ if data:
+ self.update(data)
+
+ def update(self, data):
+ """Continue hashing of a message by consuming the next chunk of data.
+
+ Repeated calls are equivalent to a single call with the concatenation
+ of all the arguments. In other words:
+
+ >>> m.update(a); m.update(b)
+
+ is equivalent to:
+
+ >>> m.update(a+b)
+
+ :Parameters:
+ data : byte string
+ The next chunk of the message being hashed.
+ """
+ return self._hash.update(data)
+
+ def digest(self):
+ """Return the **binary** (non-printable) digest of the message that has been hashed so far.
+
+ This method does not change the state of the hash object.
+ You can continue updating the object after calling this function.
+
+ :Return: A byte string of `digest_size` bytes. It may contain non-ASCII
+ characters, including null bytes.
+ """
+ return self._hash.digest()
+
+ def hexdigest(self):
+ """Return the **printable** digest of the message that has been hashed so far.
+
+ This method does not change the state of the hash object.
+
+ :Return: A string of 2* `digest_size` characters. It contains only
+ hexadecimal ASCII digits.
+ """
+ return self._hash.hexdigest()
+
+ def copy(self):
+ """Return a copy ("clone") of the hash object.
+
+ The copy will have the same internal state as the original hash
+ object.
+ This can be used to efficiently compute the digests of strings that
+ share a common initial substring.
+
+ :Return: A hash object of the same type
+ """
+ return self._hash.copy()
+
+ def new(self, data=None):
+ """Return a fresh instance of the hash object.
+
+ Unlike the `copy` method, the internal state of the object is empty.
+
+ :Parameters:
+ data : byte string
+ The next chunk of the message being hashed.
+
+ :Return: A hash object of the same type
+ """
+ pass
+
diff --git a/lib/Python/Lib/Crypto/Protocol/AllOrNothing.py b/lib/Python/Lib/Crypto/Protocol/AllOrNothing.py
new file mode 100644
index 000000000..acc92d5cd
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Protocol/AllOrNothing.py
@@ -0,0 +1,319 @@
+#
+# AllOrNothing.py : all-or-nothing package transformations
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew M. Kuchling and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""This file implements all-or-nothing package transformations.
+
+An all-or-nothing package transformation is one in which some text is
+transformed into message blocks, such that all blocks must be obtained before
+the reverse transformation can be applied. Thus, if any blocks are corrupted
+or lost, the original message cannot be reproduced.
+
+An all-or-nothing package transformation is not encryption, although a block
+cipher algorithm is used. The encryption key is randomly generated and is
+extractable from the message blocks.
+
+This class implements the All-Or-Nothing package transformation algorithm
+described in:
+
+Ronald L. Rivest. "All-Or-Nothing Encryption and The Package Transform"
+http://theory.lcs.mit.edu/~rivest/fusion.pdf
+
+"""
+
+__revision__ = "$Id$"
+
+import operator
+import sys
+from Crypto.Util.number import bytes_to_long, long_to_bytes
+from Crypto.Util.py3compat import *
+
+def isInt(x):
+ test = 0
+ try:
+ test += x
+ except TypeError:
+ return 0
+ return 1
+
+class AllOrNothing:
+ """Class implementing the All-or-Nothing package transform.
+
+ Methods for subclassing:
+
+ _inventkey(key_size):
+ Returns a randomly generated key. Subclasses can use this to
+ implement better random key generating algorithms. The default
+ algorithm is probably not very cryptographically secure.
+
+ """
+
+ def __init__(self, ciphermodule, mode=None, IV=None):
+ """AllOrNothing(ciphermodule, mode=None, IV=None)
+
+ ciphermodule is a module implementing the cipher algorithm to
+ use. It must provide the PEP272 interface.
+
+ Note that the encryption key is randomly generated
+ automatically when needed. Optional arguments mode and IV are
+ passed directly through to the ciphermodule.new() method; they
+ are the feedback mode and initialization vector to use. All
+ three arguments must be the same for the object used to create
+ the digest, and to undigest'ify the message blocks.
+ """
+
+ self.__ciphermodule = ciphermodule
+ self.__mode = mode
+ self.__IV = IV
+ self.__key_size = ciphermodule.key_size
+ if not isInt(self.__key_size) or self.__key_size==0:
+ self.__key_size = 16
+
+ __K0digit = bchr(0x69)
+
+ def digest(self, text):
+ """digest(text:string) : [string]
+
+ Perform the All-or-Nothing package transform on the given
+ string. Output is a list of message blocks describing the
+ transformed text, where each block is a string of bit length equal
+ to the ciphermodule's block_size.
+ """
+
+ # generate a random session key and K0, the key used to encrypt the
+ # hash blocks. Rivest calls this a fixed, publically-known encryption
+ # key, but says nothing about the security implications of this key or
+ # how to choose it.
+ key = self._inventkey(self.__key_size)
+ K0 = self.__K0digit * self.__key_size
+
+ # we need two cipher objects here, one that is used to encrypt the
+ # message blocks and one that is used to encrypt the hashes. The
+ # former uses the randomly generated key, while the latter uses the
+ # well-known key.
+ mcipher = self.__newcipher(key)
+ hcipher = self.__newcipher(K0)
+
+ # Pad the text so that its length is a multiple of the cipher's
+ # block_size. Pad with trailing spaces, which will be eliminated in
+ # the undigest() step.
+ block_size = self.__ciphermodule.block_size
+ padbytes = block_size - (len(text) % block_size)
+ text = text + b(' ') * padbytes
+
+ # Run through the algorithm:
+ # s: number of message blocks (size of text / block_size)
+ # input sequence: m1, m2, ... ms
+ # random key K' (`key' in the code)
+ # Compute output sequence: m'1, m'2, ... m's' for s' = s + 1
+ # Let m'i = mi ^ E(K', i) for i = 1, 2, 3, ..., s
+ # Let m's' = K' ^ h1 ^ h2 ^ ... hs
+ # where hi = E(K0, m'i ^ i) for i = 1, 2, ... s
+ #
+ # The one complication I add is that the last message block is hard
+ # coded to the number of padbytes added, so that these can be stripped
+ # during the undigest() step
+ s = divmod(len(text), block_size)[0]
+ blocks = []
+ hashes = []
+ for i in range(1, s+1):
+ start = (i-1) * block_size
+ end = start + block_size
+ mi = text[start:end]
+ assert len(mi) == block_size
+ cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
+ mticki = bytes_to_long(mi) ^ bytes_to_long(cipherblock)
+ blocks.append(mticki)
+ # calculate the hash block for this block
+ hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size))
+ hashes.append(bytes_to_long(hi))
+
+ # Add the padbytes length as a message block
+ i = i + 1
+ cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
+ mticki = padbytes ^ bytes_to_long(cipherblock)
+ blocks.append(mticki)
+
+ # calculate this block's hash
+ hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size))
+ hashes.append(bytes_to_long(hi))
+
+ # Now calculate the last message block of the sequence 1..s'. This
+ # will contain the random session key XOR'd with all the hash blocks,
+ # so that for undigest(), once all the hash blocks are calculated, the
+ # session key can be trivially extracted. Calculating all the hash
+ # blocks requires that all the message blocks be received, thus the
+ # All-or-Nothing algorithm succeeds.
+ mtick_stick = bytes_to_long(key) ^ reduce(operator.xor, hashes)
+ blocks.append(mtick_stick)
+
+ # we convert the blocks to strings since in Python, byte sequences are
+ # always represented as strings. This is more consistent with the
+ # model that encryption and hash algorithms always operate on strings.
+ return [long_to_bytes(i,self.__ciphermodule.block_size) for i in blocks]
+
+
+ def undigest(self, blocks):
+ """undigest(blocks : [string]) : string
+
+ Perform the reverse package transformation on a list of message
+ blocks. Note that the ciphermodule used for both transformations
+ must be the same. blocks is a list of strings of bit length
+ equal to the ciphermodule's block_size.
+ """
+
+ # better have at least 2 blocks, for the padbytes package and the hash
+ # block accumulator
+ if len(blocks) < 2:
+ raise ValueError, "List must be at least length 2."
+
+ # blocks is a list of strings. We need to deal with them as long
+ # integers
+ blocks = map(bytes_to_long, blocks)
+
+ # Calculate the well-known key, to which the hash blocks are
+ # encrypted, and create the hash cipher.
+ K0 = self.__K0digit * self.__key_size
+ hcipher = self.__newcipher(K0)
+ block_size = self.__ciphermodule.block_size
+
+ # Since we have all the blocks (or this method would have been called
+ # prematurely), we can calculate all the hash blocks.
+ hashes = []
+ for i in range(1, len(blocks)):
+ mticki = blocks[i-1] ^ i
+ hi = hcipher.encrypt(long_to_bytes(mticki, block_size))
+ hashes.append(bytes_to_long(hi))
+
+ # now we can calculate K' (key). remember the last block contains
+ # m's' which we don't include here
+ key = blocks[-1] ^ reduce(operator.xor, hashes)
+
+ # and now we can create the cipher object
+ mcipher = self.__newcipher(long_to_bytes(key, self.__key_size))
+
+ # And we can now decode the original message blocks
+ parts = []
+ for i in range(1, len(blocks)):
+ cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
+ mi = blocks[i-1] ^ bytes_to_long(cipherblock)
+ parts.append(mi)
+
+ # The last message block contains the number of pad bytes appended to
+ # the original text string, such that its length was an even multiple
+ # of the cipher's block_size. This number should be small enough that
+ # the conversion from long integer to integer should never overflow
+ padbytes = int(parts[-1])
+ text = b('').join(map(long_to_bytes, parts[:-1]))
+ return text[:-padbytes]
+
+ def _inventkey(self, key_size):
+ # Return key_size random bytes
+ from Crypto import Random
+ return Random.new().read(key_size)
+
+ def __newcipher(self, key):
+ if self.__mode is None and self.__IV is None:
+ return self.__ciphermodule.new(key)
+ elif self.__IV is None:
+ return self.__ciphermodule.new(key, self.__mode)
+ else:
+ return self.__ciphermodule.new(key, self.__mode, self.__IV)
+
+
+
+if __name__ == '__main__':
+ import sys
+ import getopt
+ import base64
+
+ usagemsg = '''\
+Test module usage: %(program)s [-c cipher] [-l] [-h]
+
+Where:
+ --cipher module
+ -c module
+ Cipher module to use. Default: %(ciphermodule)s
+
+ --aslong
+ -l
+ Print the encoded message blocks as long integers instead of base64
+ encoded strings
+
+ --help
+ -h
+ Print this help message
+'''
+
+ ciphermodule = 'AES'
+ aslong = 0
+
+ def usage(code, msg=None):
+ if msg:
+ print msg
+ print usagemsg % {'program': sys.argv[0],
+ 'ciphermodule': ciphermodule}
+ sys.exit(code)
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:],
+ 'c:l', ['cipher=', 'aslong'])
+ except getopt.error, msg:
+ usage(1, msg)
+
+ if args:
+ usage(1, 'Too many arguments')
+
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ elif opt in ('-c', '--cipher'):
+ ciphermodule = arg
+ elif opt in ('-l', '--aslong'):
+ aslong = 1
+
+ # ugly hack to force __import__ to give us the end-path module
+ module = __import__('Crypto.Cipher.'+ciphermodule, None, None, ['new'])
+
+ x = AllOrNothing(module)
+ print 'Original text:\n=========='
+ print __doc__
+ print '=========='
+ msgblocks = x.digest(b(__doc__))
+ print 'message blocks:'
+ for i, blk in zip(range(len(msgblocks)), msgblocks):
+ # base64 adds a trailing newline
+ print ' %3d' % i,
+ if aslong:
+ print bytes_to_long(blk)
+ else:
+ print base64.encodestring(blk)[:-1]
+ #
+ # get a new undigest-only object so there's no leakage
+ y = AllOrNothing(module)
+ text = y.undigest(msgblocks)
+ if text == b(__doc__):
+ print 'They match!'
+ else:
+ print 'They differ!'
diff --git a/lib/Python/Lib/Crypto/Protocol/Chaffing.py b/lib/Python/Lib/Crypto/Protocol/Chaffing.py
new file mode 100644
index 000000000..c19e03759
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Protocol/Chaffing.py
@@ -0,0 +1,245 @@
+#
+# Chaffing.py : chaffing & winnowing support
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew M. Kuchling, Barry A. Warsaw, and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+#
+"""This file implements the chaffing algorithm.
+
+Winnowing and chaffing is a technique for enhancing privacy without requiring
+strong encryption. In short, the technique takes a set of authenticated
+message blocks (the wheat) and adds a number of chaff blocks which have
+randomly chosen data and MAC fields. This means that to an adversary, the
+chaff blocks look as valid as the wheat blocks, and so the authentication
+would have to be performed on every block. By tailoring the number of chaff
+blocks added to the message, the sender can make breaking the message
+computationally infeasible. There are many other interesting properties of
+the winnow/chaff technique.
+
+For example, say Alice is sending a message to Bob. She packetizes the
+message and performs an all-or-nothing transformation on the packets. Then
+she authenticates each packet with a message authentication code (MAC). The
+MAC is a hash of the data packet, and there is a secret key which she must
+share with Bob (key distribution is an exercise left to the reader). She then
+adds a serial number to each packet, and sends the packets to Bob.
+
+Bob receives the packets, and using the shared secret authentication key,
+authenticates the MACs for each packet. Those packets that have bad MACs are
+simply discarded. The remainder are sorted by serial number, and passed
+through the reverse all-or-nothing transform. The transform means that an
+eavesdropper (say Eve) must acquire all the packets before any of the data can
+be read. If even one packet is missing, the data is useless.
+
+There's one twist: by adding chaff packets, Alice and Bob can make Eve's job
+much harder, since Eve now has to break the shared secret key, or try every
+combination of wheat and chaff packet to read any of the message. The cool
+thing is that Bob doesn't need to add any additional code; the chaff packets
+are already filtered out because their MACs don't match (in all likelihood --
+since the data and MACs for the chaff packets are randomly chosen it is
+possible, but very unlikely that a chaff MAC will match the chaff data). And
+Alice need not even be the party adding the chaff! She could be completely
+unaware that a third party, say Charles, is adding chaff packets to her
+messages as they are transmitted.
+
+For more information on winnowing and chaffing see this paper:
+
+Ronald L. Rivest, "Chaffing and Winnowing: Confidentiality without Encryption"
+http://theory.lcs.mit.edu/~rivest/chaffing.txt
+
+"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.number import bytes_to_long
+
+class Chaff:
+ """Class implementing the chaff adding algorithm.
+
+ Methods for subclasses:
+
+ _randnum(size):
+ Returns a randomly generated number with a byte-length equal
+ to size. Subclasses can use this to implement better random
+ data and MAC generating algorithms. The default algorithm is
+ probably not very cryptographically secure. It is most
+ important that the chaff data does not contain any patterns
+ that can be used to discern it from wheat data without running
+ the MAC.
+
+ """
+
+ def __init__(self, factor=1.0, blocksper=1):
+ """Chaff(factor:float, blocksper:int)
+
+ factor is the number of message blocks to add chaff to,
+ expressed as a percentage between 0.0 and 1.0. blocksper is
+ the number of chaff blocks to include for each block being
+ chaffed. Thus the defaults add one chaff block to every
+ message block. By changing the defaults, you can adjust how
+ computationally difficult it could be for an adversary to
+ brute-force crack the message. The difficulty is expressed
+ as:
+
+ pow(blocksper, int(factor * number-of-blocks))
+
+ For ease of implementation, when factor < 1.0, only the first
+ int(factor*number-of-blocks) message blocks are chaffed.
+ """
+
+ if not (0.0<=factor<=1.0):
+ raise ValueError, "'factor' must be between 0.0 and 1.0"
+ if blocksper < 0:
+ raise ValueError, "'blocksper' must be zero or more"
+
+ self.__factor = factor
+ self.__blocksper = blocksper
+
+
+ def chaff(self, blocks):
+ """chaff( [(serial-number:int, data:string, MAC:string)] )
+ : [(int, string, string)]
+
+ Add chaff to message blocks. blocks is a list of 3-tuples of the
+ form (serial-number, data, MAC).
+
+ Chaff is created by choosing a random number of the same
+ byte-length as data, and another random number of the same
+ byte-length as MAC. The message block's serial number is
+ placed on the chaff block and all the packet's chaff blocks
+ are randomly interspersed with the single wheat block. This
+ method then returns a list of 3-tuples of the same form.
+ Chaffed blocks will contain multiple instances of 3-tuples
+ with the same serial number, but the only way to figure out
+ which blocks are wheat and which are chaff is to perform the
+ MAC hash and compare values.
+ """
+
+ chaffedblocks = []
+
+ # count is the number of blocks to add chaff to. blocksper is the
+ # number of chaff blocks to add per message block that is being
+ # chaffed.
+ count = len(blocks) * self.__factor
+ blocksper = range(self.__blocksper)
+ for i, wheat in zip(range(len(blocks)), blocks):
+ # it shouldn't matter which of the n blocks we add chaff to, so for
+ # ease of implementation, we'll just add them to the first count
+ # blocks
+ if i < count:
+ serial, data, mac = wheat
+ datasize = len(data)
+ macsize = len(mac)
+ addwheat = 1
+ # add chaff to this block
+ for j in blocksper:
+ import sys
+ chaffdata = self._randnum(datasize)
+ chaffmac = self._randnum(macsize)
+ chaff = (serial, chaffdata, chaffmac)
+ # mix up the order, if the 5th bit is on then put the
+ # wheat on the list
+ if addwheat and bytes_to_long(self._randnum(16)) & 0x40:
+ chaffedblocks.append(wheat)
+ addwheat = 0
+ chaffedblocks.append(chaff)
+ if addwheat:
+ chaffedblocks.append(wheat)
+ else:
+ # just add the wheat
+ chaffedblocks.append(wheat)
+ return chaffedblocks
+
+ def _randnum(self, size):
+ from Crypto import Random
+ return Random.new().read(size)
+
+
+if __name__ == '__main__':
+ text = """\
+We hold these truths to be self-evident, that all men are created equal, that
+they are endowed by their Creator with certain unalienable Rights, that among
+these are Life, Liberty, and the pursuit of Happiness. That to secure these
+rights, Governments are instituted among Men, deriving their just powers from
+the consent of the governed. That whenever any Form of Government becomes
+destructive of these ends, it is the Right of the People to alter or to
+abolish it, and to institute new Government, laying its foundation on such
+principles and organizing its powers in such form, as to them shall seem most
+likely to effect their Safety and Happiness.
+"""
+ print 'Original text:\n=========='
+ print text
+ print '=========='
+
+ # first transform the text into packets
+ blocks = [] ; size = 40
+ for i in range(0, len(text), size):
+ blocks.append( text[i:i+size] )
+
+ # now get MACs for all the text blocks. The key is obvious...
+ print 'Calculating MACs...'
+ from Crypto.Hash import HMAC, SHA
+ key = 'Jefferson'
+ macs = [HMAC.new(key, block, digestmod=SHA).digest()
+ for block in blocks]
+
+ assert len(blocks) == len(macs)
+
+ # put these into a form acceptable as input to the chaffing procedure
+ source = []
+ m = zip(range(len(blocks)), blocks, macs)
+ print m
+ for i, data, mac in m:
+ source.append((i, data, mac))
+
+ # now chaff these
+ print 'Adding chaff...'
+ c = Chaff(factor=0.5, blocksper=2)
+ chaffed = c.chaff(source)
+
+ from base64 import encodestring
+
+ # print the chaffed message blocks. meanwhile, separate the wheat from
+ # the chaff
+
+ wheat = []
+ print 'chaffed message blocks:'
+ for i, data, mac in chaffed:
+ # do the authentication
+ h = HMAC.new(key, data, digestmod=SHA)
+ pmac = h.digest()
+ if pmac == mac:
+ tag = '-->'
+ wheat.append(data)
+ else:
+ tag = ' '
+ # base64 adds a trailing newline
+ print tag, '%3d' % i, \
+ repr(data), encodestring(mac)[:-1]
+
+ # now decode the message packets and check it against the original text
+ print 'Undigesting wheat...'
+ # PY3K: This is meant to be text, do not change to bytes (data)
+ newtext = "".join(wheat)
+ if newtext == text:
+ print 'They match!'
+ else:
+ print 'They differ!'
diff --git a/lib/Python/Lib/Crypto/Protocol/KDF.py b/lib/Python/Lib/Crypto/Protocol/KDF.py
new file mode 100644
index 000000000..973b7aff7
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Protocol/KDF.py
@@ -0,0 +1,123 @@
+#
+# KDF.py : a collection of Key Derivation Functions
+#
+# Part of the Python Cryptography Toolkit
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""This file contains a collection of standard key derivation functions.
+
+A key derivation function derives one or more secondary secret keys from
+one primary secret (a master key or a pass phrase).
+
+This is typically done to insulate the secondary keys from each other,
+to avoid that leakage of a secondary key compromises the security of the
+master key, or to thwart attacks on pass phrases (e.g. via rainbow tables).
+
+:undocumented: __revision__
+"""
+
+__revision__ = "$Id$"
+
+import math
+import struct
+
+from Crypto.Util.py3compat import *
+from Crypto.Hash import SHA as SHA1, HMAC
+from Crypto.Util.strxor import strxor
+
+def PBKDF1(password, salt, dkLen, count=1000, hashAlgo=None):
+ """Derive one key from a password (or passphrase).
+
+ This function performs key derivation according an old version of
+ the PKCS#5 standard (v1.5).
+
+ This algorithm is called ``PBKDF1``. Even though it is still described
+ in the latest version of the PKCS#5 standard (version 2, or RFC2898),
+ newer applications should use the more secure and versatile `PBKDF2` instead.
+
+ :Parameters:
+ password : string
+ The secret password or pass phrase to generate the key from.
+ salt : byte string
+ An 8 byte string to use for better protection from dictionary attacks.
+ This value does not need to be kept secret, but it should be randomly
+ chosen for each derivation.
+ dkLen : integer
+ The length of the desired key. Default is 16 bytes, suitable for instance for `Crypto.Cipher.AES`.
+ count : integer
+ The number of iterations to carry out. It's recommended to use at least 1000.
+ hashAlgo : module
+ The hash algorithm to use, as a module or an object from the `Crypto.Hash` package.
+ The digest length must be no shorter than ``dkLen``.
+ The default algorithm is `SHA1`.
+
+ :Return: A byte string of length `dkLen` that can be used as key.
+ """
+ if not hashAlgo:
+ hashAlgo = SHA1
+ password = tobytes(password)
+ pHash = hashAlgo.new(password+salt)
+ digest = pHash.digest_size
+ if dkLen>digest:
+ raise ValueError("Selected hash algorithm has a too short digest (%d bytes)." % digest)
+ if len(salt)!=8:
+ raise ValueError("Salt is not 8 bytes long.")
+ for i in xrange(count-1):
+ pHash = pHash.new(pHash.digest())
+ return pHash.digest()[:dkLen]
+
+def PBKDF2(password, salt, dkLen=16, count=1000, prf=None):
+ """Derive one or more keys from a password (or passphrase).
+
+ This performs key derivation according to the PKCS#5 standard (v2.0),
+ by means of the ``PBKDF2`` algorithm.
+
+ :Parameters:
+ password : string
+ The secret password or pass phrase to generate the key from.
+ salt : string
+ A string to use for better protection from dictionary attacks.
+ This value does not need to be kept secret, but it should be randomly
+ chosen for each derivation. It is recommended to be at least 8 bytes long.
+ dkLen : integer
+ The cumulative length of the desired keys. Default is 16 bytes, suitable for instance for `Crypto.Cipher.AES`.
+ count : integer
+ The number of iterations to carry out. It's recommended to use at least 1000.
+ prf : callable
+ A pseudorandom function. It must be a function that returns a pseudorandom string
+ from two parameters: a secret and a salt. If not specified, HMAC-SHA1 is used.
+
+ :Return: A byte string of length `dkLen` that can be used as key material.
+ If you wanted multiple keys, just break up this string into segments of the desired length.
+"""
+ password = tobytes(password)
+ if prf is None:
+ prf = lambda p,s: HMAC.new(p,s,SHA1).digest()
+ key = b('')
+ i = 1
+ while len(key)<dkLen:
+ U = previousU = prf(password,salt+struct.pack(">I", i))
+ for j in xrange(count-1):
+ previousU = t = prf(password,previousU)
+ U = strxor(U,t)
+ key += U
+ i = i + 1
+ return key[:dkLen]
+
diff --git a/lib/Python/Lib/Crypto/Protocol/__init__.py b/lib/Python/Lib/Crypto/Protocol/__init__.py
new file mode 100644
index 000000000..cacc685a0
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Protocol/__init__.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Cryptographic protocols
+
+Implements various cryptographic protocols. (Don't expect to find
+network protocols here.)
+
+Crypto.Protocol.AllOrNothing
+ Transforms a message into a set of message blocks, such that the blocks
+ can be recombined to get the message back.
+
+Crypto.Protocol.Chaffing
+ Takes a set of authenticated message blocks (the wheat) and adds a number
+ of randomly generated blocks (the chaff).
+
+Crypto.Protocol.KDF
+ A collection of standard key derivation functions.
+
+:undocumented: __revision__
+"""
+
+__all__ = ['AllOrNothing', 'Chaffing', 'KDF']
+__revision__ = "$Id$"
diff --git a/lib/Python/Lib/Crypto/PublicKey/DSA.py b/lib/Python/Lib/Crypto/PublicKey/DSA.py
new file mode 100644
index 000000000..d6bffd673
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/DSA.py
@@ -0,0 +1,379 @@
+# -*- coding: utf-8 -*-
+#
+# PublicKey/DSA.py : DSA signature primitive
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""DSA public-key signature algorithm.
+
+DSA_ is a widespread public-key signature algorithm. Its security is
+based on the discrete logarithm problem (DLP_). Given a cyclic
+group, a generator *g*, and an element *h*, it is hard
+to find an integer *x* such that *g^x = h*. The problem is believed
+to be difficult, and it has been proved such (and therefore secure) for
+more than 30 years.
+
+The group is actually a sub-group over the integers modulo *p*, with *p* prime.
+The sub-group order is *q*, which is prime too; it always holds that *(p-1)* is a multiple of *q*.
+The cryptographic strength is linked to the magnitude of *p* and *q*.
+The signer holds a value *x* (*0<x<q-1*) as private key, and its public
+key (*y* where *y=g^x mod p*) is distributed.
+
+In 2012, a sufficient size is deemed to be 2048 bits for *p* and 256 bits for *q*.
+For more information, see the most recent ECRYPT_ report.
+
+DSA is reasonably secure for new designs.
+
+The algorithm can only be used for authentication (digital signature).
+DSA cannot be used for confidentiality (encryption).
+
+The values *(p,q,g)* are called *domain parameters*;
+they are not sensitive but must be shared by both parties (the signer and the verifier).
+Different signers can share the same domain parameters with no security
+concerns.
+
+The DSA signature is twice as big as the size of *q* (64 bytes if *q* is 256 bit
+long).
+
+This module provides facilities for generating new DSA keys and for constructing
+them from known components. DSA keys allows you to perform basic signing and
+verification.
+
+ >>> from Crypto.Random import random
+ >>> from Crypto.PublicKey import DSA
+ >>> from Crypto.Hash import SHA
+ >>>
+ >>> message = "Hello"
+ >>> key = DSA.generate(1024)
+ >>> h = SHA.new(message).digest()
+ >>> k = random.StrongRandom().randint(1,key.q-1)
+ >>> sig = key.sign(h,k)
+ >>> ...
+ >>> if key.verify(h,sig):
+ >>> print "OK"
+ >>> else:
+ >>> print "Incorrect signature"
+
+.. _DSA: http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
+.. _DLP: http://www.cosic.esat.kuleuven.be/publications/talk-78.pdf
+.. _ECRYPT: http://www.ecrypt.eu.org/documents/D.SPA.17.pdf
+"""
+
+__revision__ = "$Id$"
+
+__all__ = ['generate', 'construct', 'error', 'DSAImplementation', '_DSAobj']
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+
+from Crypto.PublicKey import _DSA, _slowmath, pubkey
+from Crypto import Random
+
+try:
+ from Crypto.PublicKey import _fastmath
+except ImportError:
+ _fastmath = None
+
+class _DSAobj(pubkey.pubkey):
+ """Class defining an actual DSA key.
+
+ :undocumented: __getstate__, __setstate__, __repr__, __getattr__
+ """
+ #: Dictionary of DSA parameters.
+ #:
+ #: A public key will only have the following entries:
+ #:
+ #: - **y**, the public key.
+ #: - **g**, the generator.
+ #: - **p**, the modulus.
+ #: - **q**, the order of the sub-group.
+ #:
+ #: A private key will also have:
+ #:
+ #: - **x**, the private key.
+ keydata = ['y', 'g', 'p', 'q', 'x']
+
+ def __init__(self, implementation, key):
+ self.implementation = implementation
+ self.key = key
+
+ def __getattr__(self, attrname):
+ if attrname in self.keydata:
+ # For backward compatibility, allow the user to get (not set) the
+ # DSA key parameters directly from this object.
+ return getattr(self.key, attrname)
+ else:
+ raise AttributeError("%s object has no %r attribute" % (self.__class__.__name__, attrname,))
+
+ def sign(self, M, K):
+ """Sign a piece of data with DSA.
+
+ :Parameter M: The piece of data to sign with DSA. It may
+ not be longer in bit size than the sub-group order (*q*).
+ :Type M: byte string or long
+
+ :Parameter K: A secret number, chosen randomly in the closed
+ range *[1,q-1]*.
+ :Type K: long (recommended) or byte string (not recommended)
+
+ :attention: selection of *K* is crucial for security. Generating a
+ random number larger than *q* and taking the modulus by *q* is
+ **not** secure, since smaller values will occur more frequently.
+ Generating a random number systematically smaller than *q-1*
+ (e.g. *floor((q-1)/8)* random bytes) is also **not** secure. In general,
+ it shall not be possible for an attacker to know the value of `any
+ bit of K`__.
+
+ :attention: The number *K* shall not be reused for any other
+ operation and shall be discarded immediately.
+
+ :attention: M must be a digest cryptographic hash, otherwise
+ an attacker may mount an existential forgery attack.
+
+ :Return: A tuple with 2 longs.
+
+ .. __: http://www.di.ens.fr/~pnguyen/pub_NgSh00.htm
+ """
+ return pubkey.pubkey.sign(self, M, K)
+
+ def verify(self, M, signature):
+ """Verify the validity of a DSA signature.
+
+ :Parameter M: The expected message.
+ :Type M: byte string or long
+
+ :Parameter signature: The DSA signature to verify.
+ :Type signature: A tuple with 2 longs as return by `sign`
+
+ :Return: True if the signature is correct, False otherwise.
+ """
+ return pubkey.pubkey.verify(self, M, signature)
+
+ def _encrypt(self, c, K):
+ raise TypeError("DSA cannot encrypt")
+
+ def _decrypt(self, c):
+ raise TypeError("DSA cannot decrypt")
+
+ def _blind(self, m, r):
+ raise TypeError("DSA cannot blind")
+
+ def _unblind(self, m, r):
+ raise TypeError("DSA cannot unblind")
+
+ def _sign(self, m, k):
+ return self.key._sign(m, k)
+
+ def _verify(self, m, sig):
+ (r, s) = sig
+ return self.key._verify(m, r, s)
+
+ def has_private(self):
+ return self.key.has_private()
+
+ def size(self):
+ return self.key.size()
+
+ def can_blind(self):
+ return False
+
+ def can_encrypt(self):
+ return False
+
+ def can_sign(self):
+ return True
+
+ def publickey(self):
+ return self.implementation.construct((self.key.y, self.key.g, self.key.p, self.key.q))
+
+ def __getstate__(self):
+ d = {}
+ for k in self.keydata:
+ try:
+ d[k] = getattr(self.key, k)
+ except AttributeError:
+ pass
+ return d
+
+ def __setstate__(self, d):
+ if not hasattr(self, 'implementation'):
+ self.implementation = DSAImplementation()
+ t = []
+ for k in self.keydata:
+ if not d.has_key(k):
+ break
+ t.append(d[k])
+ self.key = self.implementation._math.dsa_construct(*tuple(t))
+
+ def __repr__(self):
+ attrs = []
+ for k in self.keydata:
+ if k == 'p':
+ attrs.append("p(%d)" % (self.size()+1,))
+ elif hasattr(self.key, k):
+ attrs.append(k)
+ if self.has_private():
+ attrs.append("private")
+ # PY3K: This is meant to be text, do not change to bytes (data)
+ return "<%s @0x%x %s>" % (self.__class__.__name__, id(self), ",".join(attrs))
+
+class DSAImplementation(object):
+ """
+ A DSA key factory.
+
+ This class is only internally used to implement the methods of the
+ `Crypto.PublicKey.DSA` module.
+ """
+
+ def __init__(self, **kwargs):
+ """Create a new DSA key factory.
+
+ :Keywords:
+ use_fast_math : bool
+ Specify which mathematic library to use:
+
+ - *None* (default). Use fastest math available.
+ - *True* . Use fast math.
+ - *False* . Use slow math.
+ default_randfunc : callable
+ Specify how to collect random data:
+
+ - *None* (default). Use Random.new().read().
+ - not *None* . Use the specified function directly.
+ :Raise RuntimeError:
+ When **use_fast_math** =True but fast math is not available.
+ """
+ use_fast_math = kwargs.get('use_fast_math', None)
+ if use_fast_math is None: # Automatic
+ if _fastmath is not None:
+ self._math = _fastmath
+ else:
+ self._math = _slowmath
+
+ elif use_fast_math: # Explicitly select fast math
+ if _fastmath is not None:
+ self._math = _fastmath
+ else:
+ raise RuntimeError("fast math module not available")
+
+ else: # Explicitly select slow math
+ self._math = _slowmath
+
+ self.error = self._math.error
+
+ # 'default_randfunc' parameter:
+ # None (default) - use Random.new().read
+ # not None - use the specified function
+ self._default_randfunc = kwargs.get('default_randfunc', None)
+ self._current_randfunc = None
+
+ def _get_randfunc(self, randfunc):
+ if randfunc is not None:
+ return randfunc
+ elif self._current_randfunc is None:
+ self._current_randfunc = Random.new().read
+ return self._current_randfunc
+
+ def generate(self, bits, randfunc=None, progress_func=None):
+ """Randomly generate a fresh, new DSA key.
+
+ :Parameters:
+ bits : int
+ Key length, or size (in bits) of the DSA modulus
+ *p*.
+ It must be a multiple of 64, in the closed
+ interval [512,1024].
+ randfunc : callable
+ Random number generation function; it should accept
+ a single integer N and return a string of random data
+ N bytes long.
+ If not specified, a new one will be instantiated
+ from ``Crypto.Random``.
+ progress_func : callable
+ Optional function that will be called with a short string
+ containing the key parameter currently being generated;
+ it's useful for interactive applications where a user is
+ waiting for a key to be generated.
+
+ :attention: You should always use a cryptographically secure random number generator,
+ such as the one defined in the ``Crypto.Random`` module; **don't** just use the
+ current time and the ``random`` module.
+
+ :Return: A DSA key object (`_DSAobj`).
+
+ :Raise ValueError:
+ When **bits** is too little, too big, or not a multiple of 64.
+ """
+
+ # Check against FIPS 186-2, which says that the size of the prime p
+ # must be a multiple of 64 bits between 512 and 1024
+ for i in (0, 1, 2, 3, 4, 5, 6, 7, 8):
+ if bits == 512 + 64*i:
+ return self._generate(bits, randfunc, progress_func)
+
+ # The March 2006 draft of FIPS 186-3 also allows 2048 and 3072-bit
+ # primes, but only with longer q values. Since the current DSA
+ # implementation only supports a 160-bit q, we don't support larger
+ # values.
+ raise ValueError("Number of bits in p must be a multiple of 64 between 512 and 1024, not %d bits" % (bits,))
+
+ def _generate(self, bits, randfunc=None, progress_func=None):
+ rf = self._get_randfunc(randfunc)
+ obj = _DSA.generate_py(bits, rf, progress_func) # TODO: Don't use legacy _DSA module
+ key = self._math.dsa_construct(obj.y, obj.g, obj.p, obj.q, obj.x)
+ return _DSAobj(self, key)
+
+ def construct(self, tup):
+ """Construct a DSA key from a tuple of valid DSA components.
+
+ The modulus *p* must be a prime.
+
+ The following equations must apply:
+
+ - p-1 = 0 mod q
+ - g^x = y mod p
+ - 0 < x < q
+ - 1 < g < p
+
+ :Parameters:
+ tup : tuple
+ A tuple of long integers, with 4 or 5 items
+ in the following order:
+
+ 1. Public key (*y*).
+ 2. Sub-group generator (*g*).
+ 3. Modulus, finite field order (*p*).
+ 4. Sub-group order (*q*).
+ 5. Private key (*x*). Optional.
+
+ :Return: A DSA key object (`_DSAobj`).
+ """
+ key = self._math.dsa_construct(*tup)
+ return _DSAobj(self, key)
+
+_impl = DSAImplementation()
+generate = _impl.generate
+construct = _impl.construct
+error = _impl.error
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
+
diff --git a/lib/Python/Lib/Crypto/PublicKey/ElGamal.py b/lib/Python/Lib/Crypto/PublicKey/ElGamal.py
new file mode 100644
index 000000000..99af71c44
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/ElGamal.py
@@ -0,0 +1,373 @@
+#
+# ElGamal.py : ElGamal encryption/decryption and signatures
+#
+# Part of the Python Cryptography Toolkit
+#
+# Originally written by: A.M. Kuchling
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""ElGamal public-key algorithm (randomized encryption and signature).
+
+Signature algorithm
+-------------------
+The security of the ElGamal signature scheme is based (like DSA) on the discrete
+logarithm problem (DLP_). Given a cyclic group, a generator *g*,
+and an element *h*, it is hard to find an integer *x* such that *g^x = h*.
+
+The group is the largest multiplicative sub-group of the integers modulo *p*,
+with *p* prime.
+The signer holds a value *x* (*0<x<p-1*) as private key, and its public
+key (*y* where *y=g^x mod p*) is distributed.
+
+The ElGamal signature is twice as big as *p*.
+
+Encryption algorithm
+--------------------
+The security of the ElGamal encryption scheme is based on the computational
+Diffie-Hellman problem (CDH_). Given a cyclic group, a generator *g*,
+and two integers *a* and *b*, it is difficult to find
+the element *g^{ab}* when only *g^a* and *g^b* are known, and not *a* and *b*.
+
+As before, the group is the largest multiplicative sub-group of the integers
+modulo *p*, with *p* prime.
+The receiver holds a value *a* (*0<a<p-1*) as private key, and its public key
+(*b* where *b*=g^a*) is given to the sender.
+
+The ElGamal ciphertext is twice as big as *p*.
+
+Domain parameters
+-----------------
+For both signature and encryption schemes, the values *(p,g)* are called
+*domain parameters*.
+They are not sensitive but must be distributed to all parties (senders and
+receivers).
+Different signers can share the same domain parameters, as can
+different recipients of encrypted messages.
+
+Security
+--------
+Both DLP and CDH problem are believed to be difficult, and they have been proved
+such (and therefore secure) for more than 30 years.
+
+The cryptographic strength is linked to the magnitude of *p*.
+In 2012, a sufficient size for *p* is deemed to be 2048 bits.
+For more information, see the most recent ECRYPT_ report.
+
+Even though ElGamal algorithms are in theory reasonably secure for new designs,
+in practice there are no real good reasons for using them.
+The signature is four times larger than the equivalent DSA, and the ciphertext
+is two times larger than the equivalent RSA.
+
+Functionality
+-------------
+This module provides facilities for generating new ElGamal keys and for constructing
+them from known components. ElGamal keys allows you to perform basic signing,
+verification, encryption, and decryption.
+
+ >>> from Crypto import Random
+ >>> from Crypto.Random import random
+ >>> from Crypto.PublicKey import ElGamal
+ >>> from Crypto.Util.number import GCD
+ >>> from Crypto.Hash import SHA
+ >>>
+ >>> message = "Hello"
+ >>> key = ElGamal.generate(1024, Random.new().read)
+ >>> h = SHA.new(message).digest()
+ >>> while 1:
+ >>> k = random.StrongRandom().randint(1,key.p-1)
+ >>> if GCD(k,key.p-1)==1: break
+ >>> sig = key.sign(h,k)
+ >>> ...
+ >>> if key.verify(h,sig):
+ >>> print "OK"
+ >>> else:
+ >>> print "Incorrect signature"
+
+.. _DLP: http://www.cosic.esat.kuleuven.be/publications/talk-78.pdf
+.. _CDH: http://en.wikipedia.org/wiki/Computational_Diffie%E2%80%93Hellman_assumption
+.. _ECRYPT: http://www.ecrypt.eu.org/documents/D.SPA.17.pdf
+"""
+
+__revision__ = "$Id$"
+
+__all__ = ['generate', 'construct', 'error', 'ElGamalobj']
+
+from Crypto.PublicKey.pubkey import *
+from Crypto.Util import number
+
+class error (Exception):
+ pass
+
+# Generate an ElGamal key with N bits
+def generate(bits, randfunc, progress_func=None):
+ """Randomly generate a fresh, new ElGamal key.
+
+ The key will be safe for use for both encryption and signature
+ (although it should be used for **only one** purpose).
+
+ :Parameters:
+ bits : int
+ Key length, or size (in bits) of the modulus *p*.
+ Recommended value is 2048.
+ randfunc : callable
+ Random number generation function; it should accept
+ a single integer N and return a string of random data
+ N bytes long.
+ progress_func : callable
+ Optional function that will be called with a short string
+ containing the key parameter currently being generated;
+ it's useful for interactive applications where a user is
+ waiting for a key to be generated.
+
+ :attention: You should always use a cryptographically secure random number generator,
+ such as the one defined in the ``Crypto.Random`` module; **don't** just use the
+ current time and the ``random`` module.
+
+ :Return: An ElGamal key object (`ElGamalobj`).
+ """
+ obj=ElGamalobj()
+ # Generate a safe prime p
+ # See Algorithm 4.86 in Handbook of Applied Cryptography
+ if progress_func:
+ progress_func('p\n')
+ while 1:
+ q = bignum(getPrime(bits-1, randfunc))
+ obj.p = 2*q+1
+ if number.isPrime(obj.p, randfunc=randfunc):
+ break
+ # Generate generator g
+ # See Algorithm 4.80 in Handbook of Applied Cryptography
+ # Note that the order of the group is n=p-1=2q, where q is prime
+ if progress_func:
+ progress_func('g\n')
+ while 1:
+ # We must avoid g=2 because of Bleichenbacher's attack described
+ # in "Generating ElGamal signatures without knowning the secret key",
+ # 1996
+ #
+ obj.g = number.getRandomRange(3, obj.p, randfunc)
+ safe = 1
+ if pow(obj.g, 2, obj.p)==1:
+ safe=0
+ if safe and pow(obj.g, q, obj.p)==1:
+ safe=0
+ # Discard g if it divides p-1 because of the attack described
+ # in Note 11.67 (iii) in HAC
+ if safe and divmod(obj.p-1, obj.g)[1]==0:
+ safe=0
+ # g^{-1} must not divide p-1 because of Khadir's attack
+ # described in "Conditions of the generator for forging ElGamal
+ # signature", 2011
+ ginv = number.inverse(obj.g, obj.p)
+ if safe and divmod(obj.p-1, ginv)[1]==0:
+ safe=0
+ if safe:
+ break
+ # Generate private key x
+ if progress_func:
+ progress_func('x\n')
+ obj.x=number.getRandomRange(2, obj.p-1, randfunc)
+ # Generate public key y
+ if progress_func:
+ progress_func('y\n')
+ obj.y = pow(obj.g, obj.x, obj.p)
+ return obj
+
+def construct(tup):
+ """Construct an ElGamal key from a tuple of valid ElGamal components.
+
+ The modulus *p* must be a prime.
+
+ The following conditions must apply:
+
+ - 1 < g < p-1
+ - g^{p-1} = 1 mod p
+ - 1 < x < p-1
+ - g^x = y mod p
+
+ :Parameters:
+ tup : tuple
+ A tuple of long integers, with 3 or 4 items
+ in the following order:
+
+ 1. Modulus (*p*).
+ 2. Generator (*g*).
+ 3. Public key (*y*).
+ 4. Private key (*x*). Optional.
+
+ :Return: An ElGamal key object (`ElGamalobj`).
+ """
+
+ obj=ElGamalobj()
+ if len(tup) not in [3,4]:
+ raise ValueError('argument for construct() wrong length')
+ for i in range(len(tup)):
+ field = obj.keydata[i]
+ setattr(obj, field, tup[i])
+ return obj
+
+class ElGamalobj(pubkey):
+ """Class defining an ElGamal key.
+
+ :undocumented: __getstate__, __setstate__, __repr__, __getattr__
+ """
+
+ #: Dictionary of ElGamal parameters.
+ #:
+ #: A public key will only have the following entries:
+ #:
+ #: - **y**, the public key.
+ #: - **g**, the generator.
+ #: - **p**, the modulus.
+ #:
+ #: A private key will also have:
+ #:
+ #: - **x**, the private key.
+ keydata=['p', 'g', 'y', 'x']
+
+ def encrypt(self, plaintext, K):
+ """Encrypt a piece of data with ElGamal.
+
+ :Parameter plaintext: The piece of data to encrypt with ElGamal.
+ It must be numerically smaller than the module (*p*).
+ :Type plaintext: byte string or long
+
+ :Parameter K: A secret number, chosen randomly in the closed
+ range *[1,p-2]*.
+ :Type K: long (recommended) or byte string (not recommended)
+
+ :Return: A tuple with two items. Each item is of the same type as the
+ plaintext (string or long).
+
+ :attention: selection of *K* is crucial for security. Generating a
+ random number larger than *p-1* and taking the modulus by *p-1* is
+ **not** secure, since smaller values will occur more frequently.
+ Generating a random number systematically smaller than *p-1*
+ (e.g. *floor((p-1)/8)* random bytes) is also **not** secure.
+ In general, it shall not be possible for an attacker to know
+ the value of any bit of K.
+
+ :attention: The number *K* shall not be reused for any other
+ operation and shall be discarded immediately.
+ """
+ return pubkey.encrypt(self, plaintext, K)
+
+ def decrypt(self, ciphertext):
+ """Decrypt a piece of data with ElGamal.
+
+ :Parameter ciphertext: The piece of data to decrypt with ElGamal.
+ :Type ciphertext: byte string, long or a 2-item tuple as returned
+ by `encrypt`
+
+ :Return: A byte string if ciphertext was a byte string or a tuple
+ of byte strings. A long otherwise.
+ """
+ return pubkey.decrypt(self, ciphertext)
+
+ def sign(self, M, K):
+ """Sign a piece of data with ElGamal.
+
+ :Parameter M: The piece of data to sign with ElGamal. It may
+ not be longer in bit size than *p-1*.
+ :Type M: byte string or long
+
+ :Parameter K: A secret number, chosen randomly in the closed
+ range *[1,p-2]* and such that *gcd(k,p-1)=1*.
+ :Type K: long (recommended) or byte string (not recommended)
+
+ :attention: selection of *K* is crucial for security. Generating a
+ random number larger than *p-1* and taking the modulus by *p-1* is
+ **not** secure, since smaller values will occur more frequently.
+ Generating a random number systematically smaller than *p-1*
+ (e.g. *floor((p-1)/8)* random bytes) is also **not** secure.
+ In general, it shall not be possible for an attacker to know
+ the value of any bit of K.
+
+ :attention: The number *K* shall not be reused for any other
+ operation and shall be discarded immediately.
+
+ :attention: M must be be a cryptographic hash, otherwise an
+ attacker may mount an existential forgery attack.
+
+ :Return: A tuple with 2 longs.
+ """
+ return pubkey.sign(self, M, K)
+
+ def verify(self, M, signature):
+ """Verify the validity of an ElGamal signature.
+
+ :Parameter M: The expected message.
+ :Type M: byte string or long
+
+ :Parameter signature: The ElGamal signature to verify.
+ :Type signature: A tuple with 2 longs as return by `sign`
+
+ :Return: True if the signature is correct, False otherwise.
+ """
+ return pubkey.verify(self, M, signature)
+
+ def _encrypt(self, M, K):
+ a=pow(self.g, K, self.p)
+ b=( M*pow(self.y, K, self.p) ) % self.p
+ return ( a,b )
+
+ def _decrypt(self, M):
+ if (not hasattr(self, 'x')):
+ raise TypeError('Private key not available in this object')
+ ax=pow(M[0], self.x, self.p)
+ plaintext=(M[1] * inverse(ax, self.p ) ) % self.p
+ return plaintext
+
+ def _sign(self, M, K):
+ if (not hasattr(self, 'x')):
+ raise TypeError('Private key not available in this object')
+ p1=self.p-1
+ if (GCD(K, p1)!=1):
+ raise ValueError('Bad K value: GCD(K,p-1)!=1')
+ a=pow(self.g, K, self.p)
+ t=(M-self.x*a) % p1
+ while t<0: t=t+p1
+ b=(t*inverse(K, p1)) % p1
+ return (a, b)
+
+ def _verify(self, M, sig):
+ if sig[0]<1 or sig[0]>self.p-1:
+ return 0
+ v1=pow(self.y, sig[0], self.p)
+ v1=(v1*pow(sig[0], sig[1], self.p)) % self.p
+ v2=pow(self.g, M, self.p)
+ if v1==v2:
+ return 1
+ return 0
+
+ def size(self):
+ return number.size(self.p) - 1
+
+ def has_private(self):
+ if hasattr(self, 'x'):
+ return 1
+ else:
+ return 0
+
+ def publickey(self):
+ return construct((self.p, self.g, self.y))
+
+
+object=ElGamalobj
diff --git a/lib/Python/Lib/Crypto/PublicKey/RSA.py b/lib/Python/Lib/Crypto/PublicKey/RSA.py
new file mode 100644
index 000000000..99d851ddb
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/RSA.py
@@ -0,0 +1,719 @@
+# -*- coding: utf-8 -*-
+#
+# PublicKey/RSA.py : RSA public key primitive
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""RSA public-key cryptography algorithm (signature and encryption).
+
+RSA_ is the most widespread and used public key algorithm. Its security is
+based on the difficulty of factoring large integers. The algorithm has
+withstood attacks for 30 years, and it is therefore considered reasonably
+secure for new designs.
+
+The algorithm can be used for both confidentiality (encryption) and
+authentication (digital signature). It is worth noting that signing and
+decryption are significantly slower than verification and encryption.
+The cryptograhic strength is primarily linked to the length of the modulus *n*.
+In 2012, a sufficient length is deemed to be 2048 bits. For more information,
+see the most recent ECRYPT_ report.
+
+Both RSA ciphertext and RSA signature are as big as the modulus *n* (256
+bytes if *n* is 2048 bit long).
+
+This module provides facilities for generating fresh, new RSA keys, constructing
+them from known components, exporting them, and importing them.
+
+ >>> from Crypto.PublicKey import RSA
+ >>>
+ >>> key = RSA.generate(2048)
+ >>> f = open('mykey.pem','w')
+ >>> f.write(RSA.exportKey('PEM'))
+ >>> f.close()
+ ...
+ >>> f = open('mykey.pem','r')
+ >>> key = RSA.importKey(f.read())
+
+Even though you may choose to directly use the methods of an RSA key object
+to perform the primitive cryptographic operations (e.g. `_RSAobj.encrypt`),
+it is recommended to use one of the standardized schemes instead (like
+`Crypto.Cipher.PKCS1_v1_5` or `Crypto.Signature.PKCS1_v1_5`).
+
+.. _RSA: http://en.wikipedia.org/wiki/RSA_%28algorithm%29
+.. _ECRYPT: http://www.ecrypt.eu.org/documents/D.SPA.17.pdf
+
+:sort: generate,construct,importKey,error
+"""
+
+__revision__ = "$Id$"
+
+__all__ = ['generate', 'construct', 'error', 'importKey', 'RSAImplementation', '_RSAobj']
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+#from Crypto.Util.python_compat import *
+from Crypto.Util.number import getRandomRange, bytes_to_long, long_to_bytes
+
+from Crypto.PublicKey import _RSA, _slowmath, pubkey
+from Crypto import Random
+
+from Crypto.Util.asn1 import DerObject, DerSequence, DerNull
+import binascii
+import struct
+
+from Crypto.Util.number import inverse
+
+from Crypto.Util.number import inverse
+
+try:
+ from Crypto.PublicKey import _fastmath
+except ImportError:
+ _fastmath = None
+
+class _RSAobj(pubkey.pubkey):
+ """Class defining an actual RSA key.
+
+ :undocumented: __getstate__, __setstate__, __repr__, __getattr__
+ """
+ #: Dictionary of RSA parameters.
+ #:
+ #: A public key will only have the following entries:
+ #:
+ #: - **n**, the modulus.
+ #: - **e**, the public exponent.
+ #:
+ #: A private key will also have:
+ #:
+ #: - **d**, the private exponent.
+ #: - **p**, the first factor of n.
+ #: - **q**, the second factor of n.
+ #: - **u**, the CRT coefficient (1/p) mod q.
+ keydata = ['n', 'e', 'd', 'p', 'q', 'u']
+
+ def __init__(self, implementation, key, randfunc=None):
+ self.implementation = implementation
+ self.key = key
+ if randfunc is None:
+ randfunc = Random.new().read
+ self._randfunc = randfunc
+
+ def __getattr__(self, attrname):
+ if attrname in self.keydata:
+ # For backward compatibility, allow the user to get (not set) the
+ # RSA key parameters directly from this object.
+ return getattr(self.key, attrname)
+ else:
+ raise AttributeError("%s object has no %r attribute" % (self.__class__.__name__, attrname,))
+
+ def encrypt(self, plaintext, K):
+ """Encrypt a piece of data with RSA.
+
+ :Parameter plaintext: The piece of data to encrypt with RSA. It may not
+ be numerically larger than the RSA module (**n**).
+ :Type plaintext: byte string or long
+
+ :Parameter K: A random parameter (*for compatibility only. This
+ value will be ignored*)
+ :Type K: byte string or long
+
+ :attention: this function performs the plain, primitive RSA encryption
+ (*textbook*). In real applications, you always need to use proper
+ cryptographic padding, and you should not directly encrypt data with
+ this method. Failure to do so may lead to security vulnerabilities.
+ It is recommended to use modules
+ `Crypto.Cipher.PKCS1_OAEP` or `Crypto.Cipher.PKCS1_v1_5` instead.
+
+ :Return: A tuple with two items. The first item is the ciphertext
+ of the same type as the plaintext (string or long). The second item
+ is always None.
+ """
+ return pubkey.pubkey.encrypt(self, plaintext, K)
+
+ def decrypt(self, ciphertext):
+ """Decrypt a piece of data with RSA.
+
+ Decryption always takes place with blinding.
+
+ :attention: this function performs the plain, primitive RSA decryption
+ (*textbook*). In real applications, you always need to use proper
+ cryptographic padding, and you should not directly decrypt data with
+ this method. Failure to do so may lead to security vulnerabilities.
+ It is recommended to use modules
+ `Crypto.Cipher.PKCS1_OAEP` or `Crypto.Cipher.PKCS1_v1_5` instead.
+
+ :Parameter ciphertext: The piece of data to decrypt with RSA. It may
+ not be numerically larger than the RSA module (**n**). If a tuple,
+ the first item is the actual ciphertext; the second item is ignored.
+
+ :Type ciphertext: byte string, long or a 2-item tuple as returned by
+ `encrypt`
+
+ :Return: A byte string if ciphertext was a byte string or a tuple
+ of byte strings. A long otherwise.
+ """
+ return pubkey.pubkey.decrypt(self, ciphertext)
+
+ def sign(self, M, K):
+ """Sign a piece of data with RSA.
+
+ Signing always takes place with blinding.
+
+ :attention: this function performs the plain, primitive RSA decryption
+ (*textbook*). In real applications, you always need to use proper
+ cryptographic padding, and you should not directly sign data with
+ this method. Failure to do so may lead to security vulnerabilities.
+ It is recommended to use modules
+ `Crypto.Signature.PKCS1_PSS` or `Crypto.Signature.PKCS1_v1_5` instead.
+
+ :Parameter M: The piece of data to sign with RSA. It may
+ not be numerically larger than the RSA module (**n**).
+ :Type M: byte string or long
+
+ :Parameter K: A random parameter (*for compatibility only. This
+ value will be ignored*)
+ :Type K: byte string or long
+
+ :Return: A 2-item tuple. The first item is the actual signature (a
+ long). The second item is always None.
+ """
+ return pubkey.pubkey.sign(self, M, K)
+
+ def verify(self, M, signature):
+ """Verify the validity of an RSA signature.
+
+ :attention: this function performs the plain, primitive RSA encryption
+ (*textbook*). In real applications, you always need to use proper
+ cryptographic padding, and you should not directly verify data with
+ this method. Failure to do so may lead to security vulnerabilities.
+ It is recommended to use modules
+ `Crypto.Signature.PKCS1_PSS` or `Crypto.Signature.PKCS1_v1_5` instead.
+
+ :Parameter M: The expected message.
+ :Type M: byte string or long
+
+ :Parameter signature: The RSA signature to verify. The first item of
+ the tuple is the actual signature (a long not larger than the modulus
+ **n**), whereas the second item is always ignored.
+ :Type signature: A 2-item tuple as return by `sign`
+
+ :Return: True if the signature is correct, False otherwise.
+ """
+ return pubkey.pubkey.verify(self, M, signature)
+
+ def _encrypt(self, c, K):
+ return (self.key._encrypt(c),)
+
+ def _decrypt(self, c):
+ #(ciphertext,) = c
+ (ciphertext,) = c[:1] # HACK - We should use the previous line
+ # instead, but this is more compatible and we're
+ # going to replace the Crypto.PublicKey API soon
+ # anyway.
+
+ # Blinded RSA decryption (to prevent timing attacks):
+ # Step 1: Generate random secret blinding factor r, such that 0 < r < n-1
+ r = getRandomRange(1, self.key.n-1, randfunc=self._randfunc)
+ # Step 2: Compute c' = c * r**e mod n
+ cp = self.key._blind(ciphertext, r)
+ # Step 3: Compute m' = c'**d mod n (ordinary RSA decryption)
+ mp = self.key._decrypt(cp)
+ # Step 4: Compute m = m**(r-1) mod n
+ return self.key._unblind(mp, r)
+
+ def _blind(self, m, r):
+ return self.key._blind(m, r)
+
+ def _unblind(self, m, r):
+ return self.key._unblind(m, r)
+
+ def _sign(self, m, K=None):
+ return (self.key._sign(m),)
+
+ def _verify(self, m, sig):
+ #(s,) = sig
+ (s,) = sig[:1] # HACK - We should use the previous line instead, but
+ # this is more compatible and we're going to replace
+ # the Crypto.PublicKey API soon anyway.
+ return self.key._verify(m, s)
+
+ def has_private(self):
+ return self.key.has_private()
+
+ def size(self):
+ return self.key.size()
+
+ def can_blind(self):
+ return True
+
+ def can_encrypt(self):
+ return True
+
+ def can_sign(self):
+ return True
+
+ def publickey(self):
+ return self.implementation.construct((self.key.n, self.key.e))
+
+ def __getstate__(self):
+ d = {}
+ for k in self.keydata:
+ try:
+ d[k] = getattr(self.key, k)
+ except AttributeError:
+ pass
+ return d
+
+ def __setstate__(self, d):
+ if not hasattr(self, 'implementation'):
+ self.implementation = RSAImplementation()
+ t = []
+ for k in self.keydata:
+ if not d.has_key(k):
+ break
+ t.append(d[k])
+ self.key = self.implementation._math.rsa_construct(*tuple(t))
+
+ def __repr__(self):
+ attrs = []
+ for k in self.keydata:
+ if k == 'n':
+ attrs.append("n(%d)" % (self.size()+1,))
+ elif hasattr(self.key, k):
+ attrs.append(k)
+ if self.has_private():
+ attrs.append("private")
+ # PY3K: This is meant to be text, do not change to bytes (data)
+ return "<%s @0x%x %s>" % (self.__class__.__name__, id(self), ",".join(attrs))
+
+ def exportKey(self, format='PEM', passphrase=None, pkcs=1):
+ """Export this RSA key.
+
+ :Parameter format: The format to use for wrapping the key.
+
+ - *'DER'*. Binary encoding, always unencrypted.
+ - *'PEM'*. Textual encoding, done according to `RFC1421`_/`RFC1423`_.
+ Unencrypted (default) or encrypted.
+ - *'OpenSSH'*. Textual encoding, done according to OpenSSH specification.
+ Only suitable for public keys (not private keys).
+ :Type format: string
+
+ :Parameter passphrase: In case of PEM, the pass phrase to derive the encryption key from.
+ :Type passphrase: string
+
+ :Parameter pkcs: The PKCS standard to follow for assembling the key.
+ You have two choices:
+
+ - with **1**, the public key is embedded into an X.509 `SubjectPublicKeyInfo` DER SEQUENCE.
+ The private key is embedded into a `PKCS#1`_ `RSAPrivateKey` DER SEQUENCE.
+ This mode is the default.
+ - with **8**, the private key is embedded into a `PKCS#8`_ `PrivateKeyInfo` DER SEQUENCE.
+ This mode is not available for public keys.
+
+ PKCS standards are not relevant for the *OpenSSH* format.
+ :Type pkcs: integer
+
+ :Return: A byte string with the encoded public or private half.
+ :Raise ValueError:
+ When the format is unknown.
+
+ .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt
+ .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt
+ .. _`PKCS#1`: http://www.ietf.org/rfc/rfc3447.txt
+ .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
+ """
+ if passphrase is not None:
+ passphrase = tobytes(passphrase)
+ if format=='OpenSSH':
+ eb = long_to_bytes(self.e)
+ nb = long_to_bytes(self.n)
+ if bord(eb[0]) & 0x80: eb=bchr(0x00)+eb
+ if bord(nb[0]) & 0x80: nb=bchr(0x00)+nb
+ keyparts = [ 'ssh-rsa', eb, nb ]
+ keystring = ''.join([ struct.pack(">I",len(kp))+kp for kp in keyparts])
+ return 'ssh-rsa '+binascii.b2a_base64(keystring)[:-1]
+
+ # DER format is always used, even in case of PEM, which simply
+ # encodes it into BASE64.
+ der = DerSequence()
+ if self.has_private():
+ keyType= { 1: 'RSA PRIVATE', 8: 'PRIVATE' }[pkcs]
+ der[:] = [ 0, self.n, self.e, self.d, self.p, self.q,
+ self.d % (self.p-1), self.d % (self.q-1),
+ inverse(self.q, self.p) ]
+ if pkcs==8:
+ derkey = der.encode()
+ der = DerSequence([0])
+ der.append(algorithmIdentifier)
+ der.append(DerObject('OCTET STRING', derkey).encode())
+ else:
+ keyType = "PUBLIC"
+ der.append(algorithmIdentifier)
+ bitmap = DerObject('BIT STRING')
+ derPK = DerSequence( [ self.n, self.e ] )
+ bitmap.payload = bchr(0x00) + derPK.encode()
+ der.append(bitmap.encode())
+ if format=='DER':
+ return der.encode()
+ if format=='PEM':
+ pem = b("-----BEGIN " + keyType + " KEY-----\n")
+ objenc = None
+ if passphrase and keyType.endswith('PRIVATE'):
+ # We only support 3DES for encryption
+ import Crypto.Hash.MD5
+ from Crypto.Cipher import DES3
+ from Crypto.Protocol.KDF import PBKDF1
+ salt = self._randfunc(8)
+ key = PBKDF1(passphrase, salt, 16, 1, Crypto.Hash.MD5)
+ key += PBKDF1(key+passphrase, salt, 8, 1, Crypto.Hash.MD5)
+ objenc = DES3.new(key, Crypto.Cipher.DES3.MODE_CBC, salt)
+ pem += b('Proc-Type: 4,ENCRYPTED\n')
+ pem += b('DEK-Info: DES-EDE3-CBC,') + binascii.b2a_hex(salt).upper() + b('\n\n')
+
+ binaryKey = der.encode()
+ if objenc:
+ # Add PKCS#7-like padding
+ padding = objenc.block_size-len(binaryKey)%objenc.block_size
+ binaryKey = objenc.encrypt(binaryKey+bchr(padding)*padding)
+
+ # Each BASE64 line can take up to 64 characters (=48 bytes of data)
+ chunks = [ binascii.b2a_base64(binaryKey[i:i+48]) for i in range(0, len(binaryKey), 48) ]
+ pem += b('').join(chunks)
+ pem += b("-----END " + keyType + " KEY-----")
+ return pem
+ return ValueError("Unknown key format '%s'. Cannot export the RSA key." % format)
+
+class RSAImplementation(object):
+ """
+ An RSA key factory.
+
+ This class is only internally used to implement the methods of the `Crypto.PublicKey.RSA` module.
+
+ :sort: __init__,generate,construct,importKey
+ :undocumented: _g*, _i*
+ """
+
+ def __init__(self, **kwargs):
+ """Create a new RSA key factory.
+
+ :Keywords:
+ use_fast_math : bool
+ Specify which mathematic library to use:
+
+ - *None* (default). Use fastest math available.
+ - *True* . Use fast math.
+ - *False* . Use slow math.
+ default_randfunc : callable
+ Specify how to collect random data:
+
+ - *None* (default). Use Random.new().read().
+ - not *None* . Use the specified function directly.
+ :Raise RuntimeError:
+ When **use_fast_math** =True but fast math is not available.
+ """
+ use_fast_math = kwargs.get('use_fast_math', None)
+ if use_fast_math is None: # Automatic
+ if _fastmath is not None:
+ self._math = _fastmath
+ else:
+ self._math = _slowmath
+
+ elif use_fast_math: # Explicitly select fast math
+ if _fastmath is not None:
+ self._math = _fastmath
+ else:
+ raise RuntimeError("fast math module not available")
+
+ else: # Explicitly select slow math
+ self._math = _slowmath
+
+ self.error = self._math.error
+
+ self._default_randfunc = kwargs.get('default_randfunc', None)
+ self._current_randfunc = None
+
+ def _get_randfunc(self, randfunc):
+ if randfunc is not None:
+ return randfunc
+ elif self._current_randfunc is None:
+ self._current_randfunc = Random.new().read
+ return self._current_randfunc
+
+ def generate(self, bits, randfunc=None, progress_func=None, e=65537):
+ """Randomly generate a fresh, new RSA key.
+
+ :Parameters:
+ bits : int
+ Key length, or size (in bits) of the RSA modulus.
+ It must be a multiple of 256, and no smaller than 1024.
+
+ randfunc : callable
+ Random number generation function; it should accept
+ a single integer N and return a string of random data
+ N bytes long.
+ If not specified, a new one will be instantiated
+ from ``Crypto.Random``.
+
+ progress_func : callable
+ Optional function that will be called with a short string
+ containing the key parameter currently being generated;
+ it's useful for interactive applications where a user is
+ waiting for a key to be generated.
+
+ e : int
+ Public RSA exponent. It must be an odd positive integer.
+ It is typically a small number with very few ones in its
+ binary representation.
+ The default value 65537 (= ``0b10000000000000001`` ) is a safe
+ choice: other common values are 5, 7, 17, and 257.
+
+ :attention: You should always use a cryptographically secure random number generator,
+ such as the one defined in the ``Crypto.Random`` module; **don't** just use the
+ current time and the ``random`` module.
+
+ :attention: Exponent 3 is also widely used, but it requires very special care when padding
+ the message.
+
+ :Return: An RSA key object (`_RSAobj`).
+
+ :Raise ValueError:
+ When **bits** is too little or not a multiple of 256, or when
+ **e** is not odd or smaller than 2.
+ """
+ if bits < 1024 or (bits & 0xff) != 0:
+ # pubkey.getStrongPrime doesn't like anything that's not a multiple of 256 and >= 1024
+ raise ValueError("RSA modulus length must be a multiple of 256 and >= 1024")
+ if e%2==0 or e<3:
+ raise ValueError("RSA public exponent must be a positive, odd integer larger than 2.")
+ rf = self._get_randfunc(randfunc)
+ obj = _RSA.generate_py(bits, rf, progress_func, e) # TODO: Don't use legacy _RSA module
+ key = self._math.rsa_construct(obj.n, obj.e, obj.d, obj.p, obj.q, obj.u)
+ return _RSAobj(self, key)
+
+ def construct(self, tup):
+ """Construct an RSA key from a tuple of valid RSA components.
+
+ The modulus **n** must be the product of two primes.
+ The public exponent **e** must be odd and larger than 1.
+
+ In case of a private key, the following equations must apply:
+
+ - e != 1
+ - p*q = n
+ - e*d = 1 mod (p-1)(q-1)
+ - p*u = 1 mod q
+
+ :Parameters:
+ tup : tuple
+ A tuple of long integers, with at least 2 and no
+ more than 6 items. The items come in the following order:
+
+ 1. RSA modulus (n).
+ 2. Public exponent (e).
+ 3. Private exponent (d). Only required if the key is private.
+ 4. First factor of n (p). Optional.
+ 5. Second factor of n (q). Optional.
+ 6. CRT coefficient, (1/p) mod q (u). Optional.
+
+ :Return: An RSA key object (`_RSAobj`).
+ """
+ key = self._math.rsa_construct(*tup)
+ return _RSAobj(self, key)
+
+ def _importKeyDER(self, externKey):
+ """Import an RSA key (public or private half), encoded in DER form."""
+
+ try:
+
+ der = DerSequence()
+ der.decode(externKey, True)
+
+ # Try PKCS#1 first, for a private key
+ if len(der)==9 and der.hasOnlyInts() and der[0]==0:
+ # ASN.1 RSAPrivateKey element
+ del der[6:] # Remove d mod (p-1), d mod (q-1), and q^{-1} mod p
+ der.append(inverse(der[4],der[5])) # Add p^{-1} mod q
+ del der[0] # Remove version
+ return self.construct(der[:])
+
+ # Keep on trying PKCS#1, but now for a public key
+ if len(der)==2:
+ # The DER object is an RSAPublicKey SEQUENCE with two elements
+ if der.hasOnlyInts():
+ return self.construct(der[:])
+ # The DER object is a SubjectPublicKeyInfo SEQUENCE with two elements:
+ # an 'algorithm' (or 'algorithmIdentifier') SEQUENCE and a 'subjectPublicKey' BIT STRING.
+ # 'algorithm' takes the value given a few lines above.
+ # 'subjectPublicKey' encapsulates the actual ASN.1 RSAPublicKey element.
+ if der[0]==algorithmIdentifier:
+ bitmap = DerObject()
+ bitmap.decode(der[1], True)
+ if bitmap.isType('BIT STRING') and bord(bitmap.payload[0])==0x00:
+ der.decode(bitmap.payload[1:], True)
+ if len(der)==2 and der.hasOnlyInts():
+ return self.construct(der[:])
+
+ # Try unencrypted PKCS#8
+ if der[0]==0:
+ # The second element in the SEQUENCE is algorithmIdentifier.
+ # It must say RSA (see above for description).
+ if der[1]==algorithmIdentifier:
+ privateKey = DerObject()
+ privateKey.decode(der[2], True)
+ if privateKey.isType('OCTET STRING'):
+ return self._importKeyDER(privateKey.payload)
+
+ except ValueError, IndexError:
+ pass
+
+ raise ValueError("RSA key format is not supported")
+
+ def importKey(self, externKey, passphrase=None):
+ """Import an RSA key (public or private half), encoded in standard form.
+
+ :Parameter externKey:
+ The RSA key to import, encoded as a string.
+
+ An RSA public key can be in any of the following formats:
+
+ - X.509 `subjectPublicKeyInfo` DER SEQUENCE (binary or PEM encoding)
+ - `PKCS#1`_ `RSAPublicKey` DER SEQUENCE (binary or PEM encoding)
+ - OpenSSH (textual public key only)
+
+ An RSA private key can be in any of the following formats:
+
+ - PKCS#1 `RSAPrivateKey` DER SEQUENCE (binary or PEM encoding)
+ - `PKCS#8`_ `PrivateKeyInfo` DER SEQUENCE (binary or PEM encoding)
+ - OpenSSH (textual public key only)
+
+ For details about the PEM encoding, see `RFC1421`_/`RFC1423`_.
+
+ In case of PEM encoding, the private key can be encrypted with DES or 3TDES according to a certain ``pass phrase``.
+ Only OpenSSL-compatible pass phrases are supported.
+ :Type externKey: string
+
+ :Parameter passphrase:
+ In case of an encrypted PEM key, this is the pass phrase from which the encryption key is derived.
+ :Type passphrase: string
+
+ :Return: An RSA key object (`_RSAobj`).
+
+ :Raise ValueError/IndexError/TypeError:
+ When the given key cannot be parsed (possibly because the pass phrase is wrong).
+
+ .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt
+ .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt
+ .. _`PKCS#1`: http://www.ietf.org/rfc/rfc3447.txt
+ .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
+ """
+ externKey = tobytes(externKey)
+ if passphrase is not None:
+ passphrase = tobytes(passphrase)
+
+ if externKey.startswith(b('-----')):
+ # This is probably a PEM encoded key
+ lines = externKey.replace(b(" "),b('')).split()
+ keyobj = None
+
+ # The encrypted PEM format
+ if lines[1].startswith(b('Proc-Type:4,ENCRYPTED')):
+ DEK = lines[2].split(b(':'))
+ if len(DEK)!=2 or DEK[0]!=b('DEK-Info') or not passphrase:
+ raise ValueError("PEM encryption format not supported.")
+ algo, salt = DEK[1].split(b(','))
+ salt = binascii.a2b_hex(salt)
+ import Crypto.Hash.MD5
+ from Crypto.Cipher import DES, DES3
+ from Crypto.Protocol.KDF import PBKDF1
+ if algo==b("DES-CBC"):
+ # This is EVP_BytesToKey in OpenSSL
+ key = PBKDF1(passphrase, salt, 8, 1, Crypto.Hash.MD5)
+ keyobj = DES.new(key, Crypto.Cipher.DES.MODE_CBC, salt)
+ elif algo==b("DES-EDE3-CBC"):
+ # Note that EVP_BytesToKey is note exactly the same as PBKDF1
+ key = PBKDF1(passphrase, salt, 16, 1, Crypto.Hash.MD5)
+ key += PBKDF1(key+passphrase, salt, 8, 1, Crypto.Hash.MD5)
+ keyobj = DES3.new(key, Crypto.Cipher.DES3.MODE_CBC, salt)
+ else:
+ raise ValueError("Unsupport PEM encryption algorithm.")
+ lines = lines[2:]
+
+ der = binascii.a2b_base64(b('').join(lines[1:-1]))
+ if keyobj:
+ der = keyobj.decrypt(der)
+ padding = bord(der[-1])
+ der = der[:-padding]
+ return self._importKeyDER(der)
+
+ if externKey.startswith(b('ssh-rsa ')):
+ # This is probably an OpenSSH key
+ keystring = binascii.a2b_base64(externKey.split(b(' '))[1])
+ keyparts = []
+ while len(keystring)>4:
+ l = struct.unpack(">I",keystring[:4])[0]
+ keyparts.append(keystring[4:4+l])
+ keystring = keystring[4+l:]
+ e = bytes_to_long(keyparts[1])
+ n = bytes_to_long(keyparts[2])
+ return self.construct([n, e])
+ if bord(externKey[0])==0x30:
+ # This is probably a DER encoded key
+ return self._importKeyDER(externKey)
+
+ raise ValueError("RSA key format is not supported")
+
+#: This is the ASN.1 DER object that qualifies an algorithm as
+#: compliant to PKCS#1 (that is, the standard RSA).
+# It is found in all 'algorithm' fields (also called 'algorithmIdentifier').
+# It is a SEQUENCE with the oid assigned to RSA and with its parameters (none).
+# 0x06 0x09 OBJECT IDENTIFIER, 9 bytes of payload
+# 0x2A 0x86 0x48 0x86 0xF7 0x0D 0x01 0x01 0x01
+# rsaEncryption (1 2 840 113549 1 1 1) (PKCS #1)
+# 0x05 0x00 NULL
+algorithmIdentifier = DerSequence(
+ [ b('\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01'),
+ DerNull().encode() ]
+ ).encode()
+
+_impl = RSAImplementation()
+#:
+#: Randomly generate a fresh, new RSA key object.
+#:
+#: See `RSAImplementation.generate`.
+#:
+generate = _impl.generate
+#:
+#: Construct an RSA key object from a tuple of valid RSA components.
+#:
+#: See `RSAImplementation.construct`.
+#:
+construct = _impl.construct
+#:
+#: Import an RSA key (public or private half), encoded in standard form.
+#:
+#: See `RSAImplementation.importKey`.
+#:
+importKey = _impl.importKey
+error = _impl.error
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
+
diff --git a/lib/Python/Lib/Crypto/PublicKey/_DSA.py b/lib/Python/Lib/Crypto/PublicKey/_DSA.py
new file mode 100644
index 000000000..6b7a96491
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/_DSA.py
@@ -0,0 +1,115 @@
+
+#
+# DSA.py : Digital Signature Algorithm
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew Kuchling, Paul Swartz, and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+#
+
+__revision__ = "$Id$"
+
+from Crypto.PublicKey.pubkey import *
+from Crypto.Util import number
+from Crypto.Util.number import bytes_to_long, long_to_bytes
+from Crypto.Hash import SHA
+from Crypto.Util.py3compat import *
+
+class error (Exception):
+ pass
+
+def generateQ(randfunc):
+ S=randfunc(20)
+ hash1=SHA.new(S).digest()
+ hash2=SHA.new(long_to_bytes(bytes_to_long(S)+1)).digest()
+ q = bignum(0)
+ for i in range(0,20):
+ c=bord(hash1[i])^bord(hash2[i])
+ if i==0:
+ c=c | 128
+ if i==19:
+ c= c | 1
+ q=q*256+c
+ while (not isPrime(q)):
+ q=q+2
+ if pow(2,159L) < q < pow(2,160L):
+ return S, q
+ raise RuntimeError('Bad q value generated')
+
+def generate_py(bits, randfunc, progress_func=None):
+ """generate(bits:int, randfunc:callable, progress_func:callable)
+
+ Generate a DSA key of length 'bits', using 'randfunc' to get
+ random data and 'progress_func', if present, to display
+ the progress of the key generation.
+ """
+
+ if bits<160:
+ raise ValueError('Key length < 160 bits')
+ obj=DSAobj()
+ # Generate string S and prime q
+ if progress_func:
+ progress_func('p,q\n')
+ while (1):
+ S, obj.q = generateQ(randfunc)
+ n=divmod(bits-1, 160)[0]
+ C, N, V = 0, 2, {}
+ b=(obj.q >> 5) & 15
+ powb=pow(bignum(2), b)
+ powL1=pow(bignum(2), bits-1)
+ while C<4096:
+ for k in range(0, n+1):
+ V[k]=bytes_to_long(SHA.new(S+bstr(N)+bstr(k)).digest())
+ W=V[n] % powb
+ for k in range(n-1, -1, -1):
+ W=(W<<160L)+V[k]
+ X=W+powL1
+ p=X-(X%(2*obj.q)-1)
+ if powL1<=p and isPrime(p):
+ break
+ C, N = C+1, N+n+1
+ if C<4096:
+ break
+ if progress_func:
+ progress_func('4096 multiples failed\n')
+
+ obj.p = p
+ power=divmod(p-1, obj.q)[0]
+ if progress_func:
+ progress_func('h,g\n')
+ while (1):
+ h=bytes_to_long(randfunc(bits)) % (p-1)
+ g=pow(h, power, p)
+ if 1<h<p-1 and g>1:
+ break
+ obj.g=g
+ if progress_func:
+ progress_func('x,y\n')
+ while (1):
+ x=bytes_to_long(randfunc(20))
+ if 0 < x < obj.q:
+ break
+ obj.x, obj.y = x, pow(g, x, p)
+ return obj
+
+class DSAobj:
+ pass
+
diff --git a/lib/Python/Lib/Crypto/PublicKey/_RSA.py b/lib/Python/Lib/Crypto/PublicKey/_RSA.py
new file mode 100644
index 000000000..9366d19de
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/_RSA.py
@@ -0,0 +1,81 @@
+#
+# RSA.py : RSA encryption/decryption
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew Kuchling, Paul Swartz, and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+#
+
+__revision__ = "$Id$"
+
+from Crypto.PublicKey import pubkey
+from Crypto.Util import number
+
+def generate_py(bits, randfunc, progress_func=None, e=65537):
+ """generate(bits:int, randfunc:callable, progress_func:callable, e:int)
+
+ Generate an RSA key of length 'bits', public exponent 'e'(which must be
+ odd), using 'randfunc' to get random data and 'progress_func',
+ if present, to display the progress of the key generation.
+ """
+ obj=RSAobj()
+ obj.e = long(e)
+
+ # Generate the prime factors of n
+ if progress_func:
+ progress_func('p,q\n')
+ p = q = 1L
+ while number.size(p*q) < bits:
+ # Note that q might be one bit longer than p if somebody specifies an odd
+ # number of bits for the key. (Why would anyone do that? You don't get
+ # more security.)
+ p = pubkey.getStrongPrime(bits>>1, obj.e, 1e-12, randfunc)
+ q = pubkey.getStrongPrime(bits - (bits>>1), obj.e, 1e-12, randfunc)
+
+ # It's OK for p to be larger than q, but let's be
+ # kind to the function that will invert it for
+ # th calculation of u.
+ if p > q:
+ (p, q)=(q, p)
+ obj.p = p
+ obj.q = q
+
+ if progress_func:
+ progress_func('u\n')
+ obj.u = pubkey.inverse(obj.p, obj.q)
+ obj.n = obj.p*obj.q
+
+ if progress_func:
+ progress_func('d\n')
+ obj.d=pubkey.inverse(obj.e, (obj.p-1)*(obj.q-1))
+
+ assert bits <= 1+obj.size(), "Generated key is too small"
+
+ return obj
+
+class RSAobj(pubkey.pubkey):
+
+ def size(self):
+ """size() : int
+ Return the maximum number of bits that can be handled by this key.
+ """
+ return number.size(self.n) - 1
+
diff --git a/lib/Python/Lib/Crypto/PublicKey/__init__.py b/lib/Python/Lib/Crypto/PublicKey/__init__.py
new file mode 100644
index 000000000..503809fa7
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/__init__.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Public-key encryption and signature algorithms.
+
+Public-key encryption uses two different keys, one for encryption and
+one for decryption. The encryption key can be made public, and the
+decryption key is kept private. Many public-key algorithms can also
+be used to sign messages, and some can *only* be used for signatures.
+
+======================== =============================================
+Module Description
+======================== =============================================
+Crypto.PublicKey.DSA Digital Signature Algorithm (Signature only)
+Crypto.PublicKey.ElGamal (Signing and encryption)
+Crypto.PublicKey.RSA (Signing, encryption, and blinding)
+======================== =============================================
+
+:undocumented: _DSA, _RSA, _fastmath, _slowmath, pubkey
+"""
+
+__all__ = ['RSA', 'DSA', 'ElGamal']
+__revision__ = "$Id$"
+
diff --git a/lib/Python/Lib/Crypto/PublicKey/_slowmath.py b/lib/Python/Lib/Crypto/PublicKey/_slowmath.py
new file mode 100644
index 000000000..d926596e2
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/_slowmath.py
@@ -0,0 +1,187 @@
+# -*- coding: utf-8 -*-
+#
+# PubKey/RSA/_slowmath.py : Pure Python implementation of the RSA portions of _fastmath
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Pure Python implementation of the RSA-related portions of Crypto.PublicKey._fastmath."""
+
+__revision__ = "$Id$"
+
+__all__ = ['rsa_construct']
+
+import sys
+
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.number import size, inverse, GCD
+
+class error(Exception):
+ pass
+
+class _RSAKey(object):
+ def _blind(self, m, r):
+ # compute r**e * m (mod n)
+ return m * pow(r, self.e, self.n)
+
+ def _unblind(self, m, r):
+ # compute m / r (mod n)
+ return inverse(r, self.n) * m % self.n
+
+ def _decrypt(self, c):
+ # compute c**d (mod n)
+ if not self.has_private():
+ raise TypeError("No private key")
+ if (hasattr(self,'p') and hasattr(self,'q') and hasattr(self,'u')):
+ m1 = pow(c, self.d % (self.p-1), self.p)
+ m2 = pow(c, self.d % (self.q-1), self.q)
+ h = m2 - m1
+ if (h<0):
+ h = h + self.q
+ h = h*self.u % self.q
+ return h*self.p+m1
+ return pow(c, self.d, self.n)
+
+ def _encrypt(self, m):
+ # compute m**d (mod n)
+ return pow(m, self.e, self.n)
+
+ def _sign(self, m): # alias for _decrypt
+ if not self.has_private():
+ raise TypeError("No private key")
+ return self._decrypt(m)
+
+ def _verify(self, m, sig):
+ return self._encrypt(sig) == m
+
+ def has_private(self):
+ return hasattr(self, 'd')
+
+ def size(self):
+ """Return the maximum number of bits that can be encrypted"""
+ return size(self.n) - 1
+
+def rsa_construct(n, e, d=None, p=None, q=None, u=None):
+ """Construct an RSAKey object"""
+ assert isinstance(n, long)
+ assert isinstance(e, long)
+ assert isinstance(d, (long, type(None)))
+ assert isinstance(p, (long, type(None)))
+ assert isinstance(q, (long, type(None)))
+ assert isinstance(u, (long, type(None)))
+ obj = _RSAKey()
+ obj.n = n
+ obj.e = e
+ if d is None:
+ return obj
+ obj.d = d
+ if p is not None and q is not None:
+ obj.p = p
+ obj.q = q
+ else:
+ # Compute factors p and q from the private exponent d.
+ # We assume that n has no more than two factors.
+ # See 8.2.2(i) in Handbook of Applied Cryptography.
+ ktot = d*e-1
+ # The quantity d*e-1 is a multiple of phi(n), even,
+ # and can be represented as t*2^s.
+ t = ktot
+ while t%2==0:
+ t=divmod(t,2)[0]
+ # Cycle through all multiplicative inverses in Zn.
+ # The algorithm is non-deterministic, but there is a 50% chance
+ # any candidate a leads to successful factoring.
+ # See "Digitalized Signatures and Public Key Functions as Intractable
+ # as Factorization", M. Rabin, 1979
+ spotted = 0
+ a = 2
+ while not spotted and a<100:
+ k = t
+ # Cycle through all values a^{t*2^i}=a^k
+ while k<ktot:
+ cand = pow(a,k,n)
+ # Check if a^k is a non-trivial root of unity (mod n)
+ if cand!=1 and cand!=(n-1) and pow(cand,2,n)==1:
+ # We have found a number such that (cand-1)(cand+1)=0 (mod n).
+ # Either of the terms divides n.
+ obj.p = GCD(cand+1,n)
+ spotted = 1
+ break
+ k = k*2
+ # This value was not any good... let's try another!
+ a = a+2
+ if not spotted:
+ raise ValueError("Unable to compute factors p and q from exponent d.")
+ # Found !
+ assert ((n % obj.p)==0)
+ obj.q = divmod(n,obj.p)[0]
+ if u is not None:
+ obj.u = u
+ else:
+ obj.u = inverse(obj.p, obj.q)
+ return obj
+
+class _DSAKey(object):
+ def size(self):
+ """Return the maximum number of bits that can be encrypted"""
+ return size(self.p) - 1
+
+ def has_private(self):
+ return hasattr(self, 'x')
+
+ def _sign(self, m, k): # alias for _decrypt
+ # SECURITY TODO - We _should_ be computing SHA1(m), but we don't because that's the API.
+ if not self.has_private():
+ raise TypeError("No private key")
+ if not (1L < k < self.q):
+ raise ValueError("k is not between 2 and q-1")
+ inv_k = inverse(k, self.q) # Compute k**-1 mod q
+ r = pow(self.g, k, self.p) % self.q # r = (g**k mod p) mod q
+ s = (inv_k * (m + self.x * r)) % self.q
+ return (r, s)
+
+ def _verify(self, m, r, s):
+ # SECURITY TODO - We _should_ be computing SHA1(m), but we don't because that's the API.
+ if not (0 < r < self.q) or not (0 < s < self.q):
+ return False
+ w = inverse(s, self.q)
+ u1 = (m*w) % self.q
+ u2 = (r*w) % self.q
+ v = (pow(self.g, u1, self.p) * pow(self.y, u2, self.p) % self.p) % self.q
+ return v == r
+
+def dsa_construct(y, g, p, q, x=None):
+ assert isinstance(y, long)
+ assert isinstance(g, long)
+ assert isinstance(p, long)
+ assert isinstance(q, long)
+ assert isinstance(x, (long, type(None)))
+ obj = _DSAKey()
+ obj.y = y
+ obj.g = g
+ obj.p = p
+ obj.q = q
+ if x is not None: obj.x = x
+ return obj
+
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
+
diff --git a/lib/Python/Lib/Crypto/PublicKey/pubkey.py b/lib/Python/Lib/Crypto/PublicKey/pubkey.py
new file mode 100644
index 000000000..e44de8fca
--- /dev/null
+++ b/lib/Python/Lib/Crypto/PublicKey/pubkey.py
@@ -0,0 +1,240 @@
+#
+# pubkey.py : Internal functions for public key operations
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew Kuchling, Paul Swartz, and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+#
+
+__revision__ = "$Id$"
+
+import types, warnings
+from Crypto.Util.number import *
+
+# Basic public key class
+class pubkey:
+ """An abstract class for a public key object.
+
+ :undocumented: __getstate__, __setstate__, __eq__, __ne__, validate
+ """
+ def __init__(self):
+ pass
+
+ def __getstate__(self):
+ """To keep key objects platform-independent, the key data is
+ converted to standard Python long integers before being
+ written out. It will then be reconverted as necessary on
+ restoration."""
+ d=self.__dict__
+ for key in self.keydata:
+ if d.has_key(key): d[key]=long(d[key])
+ return d
+
+ def __setstate__(self, d):
+ """On unpickling a key object, the key data is converted to the big
+number representation being used, whether that is Python long
+integers, MPZ objects, or whatever."""
+ for key in self.keydata:
+ if d.has_key(key): self.__dict__[key]=bignum(d[key])
+
+ def encrypt(self, plaintext, K):
+ """Encrypt a piece of data.
+
+ :Parameter plaintext: The piece of data to encrypt.
+ :Type plaintext: byte string or long
+
+ :Parameter K: A random parameter required by some algorithms
+ :Type K: byte string or long
+
+ :Return: A tuple with two items. Each item is of the same type as the
+ plaintext (string or long).
+ """
+ wasString=0
+ if isinstance(plaintext, types.StringType):
+ plaintext=bytes_to_long(plaintext) ; wasString=1
+ if isinstance(K, types.StringType):
+ K=bytes_to_long(K)
+ ciphertext=self._encrypt(plaintext, K)
+ if wasString: return tuple(map(long_to_bytes, ciphertext))
+ else: return ciphertext
+
+ def decrypt(self, ciphertext):
+ """Decrypt a piece of data.
+
+ :Parameter ciphertext: The piece of data to decrypt.
+ :Type ciphertext: byte string, long or a 2-item tuple as returned by `encrypt`
+
+ :Return: A byte string if ciphertext was a byte string or a tuple
+ of byte strings. A long otherwise.
+ """
+ wasString=0
+ if not isinstance(ciphertext, types.TupleType):
+ ciphertext=(ciphertext,)
+ if isinstance(ciphertext[0], types.StringType):
+ ciphertext=tuple(map(bytes_to_long, ciphertext)) ; wasString=1
+ plaintext=self._decrypt(ciphertext)
+ if wasString: return long_to_bytes(plaintext)
+ else: return plaintext
+
+ def sign(self, M, K):
+ """Sign a piece of data.
+
+ :Parameter M: The piece of data to encrypt.
+ :Type M: byte string or long
+
+ :Parameter K: A random parameter required by some algorithms
+ :Type K: byte string or long
+
+ :Return: A tuple with two items.
+ """
+ if (not self.has_private()):
+ raise TypeError('Private key not available in this object')
+ if isinstance(M, types.StringType): M=bytes_to_long(M)
+ if isinstance(K, types.StringType): K=bytes_to_long(K)
+ return self._sign(M, K)
+
+ def verify (self, M, signature):
+ """Verify the validity of a signature.
+
+ :Parameter M: The expected message.
+ :Type M: byte string or long
+
+ :Parameter signature: The signature to verify.
+ :Type signature: tuple with two items, as return by `sign`
+
+ :Return: True if the signature is correct, False otherwise.
+ """
+ if isinstance(M, types.StringType): M=bytes_to_long(M)
+ return self._verify(M, signature)
+
+ # alias to compensate for the old validate() name
+ def validate (self, M, signature):
+ warnings.warn("validate() method name is obsolete; use verify()",
+ DeprecationWarning)
+
+ def blind(self, M, B):
+ """Blind a message to prevent certain side-channel attacks.
+
+ :Parameter M: The message to blind.
+ :Type M: byte string or long
+
+ :Parameter B: Blinding factor.
+ :Type B: byte string or long
+
+ :Return: A byte string if M was so. A long otherwise.
+ """
+ wasString=0
+ if isinstance(M, types.StringType):
+ M=bytes_to_long(M) ; wasString=1
+ if isinstance(B, types.StringType): B=bytes_to_long(B)
+ blindedmessage=self._blind(M, B)
+ if wasString: return long_to_bytes(blindedmessage)
+ else: return blindedmessage
+
+ def unblind(self, M, B):
+ """Unblind a message after cryptographic processing.
+
+ :Parameter M: The encoded message to unblind.
+ :Type M: byte string or long
+
+ :Parameter B: Blinding factor.
+ :Type B: byte string or long
+ """
+ wasString=0
+ if isinstance(M, types.StringType):
+ M=bytes_to_long(M) ; wasString=1
+ if isinstance(B, types.StringType): B=bytes_to_long(B)
+ unblindedmessage=self._unblind(M, B)
+ if wasString: return long_to_bytes(unblindedmessage)
+ else: return unblindedmessage
+
+
+ # The following methods will usually be left alone, except for
+ # signature-only algorithms. They both return Boolean values
+ # recording whether this key's algorithm can sign and encrypt.
+ def can_sign (self):
+ """Tell if the algorithm can deal with cryptographic signatures.
+
+ This property concerns the *algorithm*, not the key itself.
+ It may happen that this particular key object hasn't got
+ the private information required to generate a signature.
+
+ :Return: boolean
+ """
+ return 1
+
+ def can_encrypt (self):
+ """Tell if the algorithm can deal with data encryption.
+
+ This property concerns the *algorithm*, not the key itself.
+ It may happen that this particular key object hasn't got
+ the private information required to decrypt data.
+
+ :Return: boolean
+ """
+ return 1
+
+ def can_blind (self):
+ """Tell if the algorithm can deal with data blinding.
+
+ This property concerns the *algorithm*, not the key itself.
+ It may happen that this particular key object hasn't got
+ the private information required carry out blinding.
+
+ :Return: boolean
+ """
+ return 0
+
+ # The following methods will certainly be overridden by
+ # subclasses.
+
+ def size (self):
+ """Tell the maximum number of bits that can be handled by this key.
+
+ :Return: int
+ """
+ return 0
+
+ def has_private (self):
+ """Tell if the key object contains private components.
+
+ :Return: bool
+ """
+ return 0
+
+ def publickey (self):
+ """Construct a new key carrying only the public information.
+
+ :Return: A new `pubkey` object.
+ """
+ return self
+
+ def __eq__ (self, other):
+ """__eq__(other): 0, 1
+ Compare us to other for equality.
+ """
+ return self.__getstate__() == other.__getstate__()
+
+ def __ne__ (self, other):
+ """__ne__(other): 0, 1
+ Compare us to other for inequality.
+ """
+ return not self.__eq__(other)
diff --git a/lib/Python/Lib/Crypto/Random/Fortuna/FortunaAccumulator.py b/lib/Python/Lib/Crypto/Random/Fortuna/FortunaAccumulator.py
new file mode 100644
index 000000000..6ffbdc5a2
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/Fortuna/FortunaAccumulator.py
@@ -0,0 +1,171 @@
+# -*- coding: ascii -*-
+#
+# FortunaAccumulator.py : Fortuna's internal accumulator
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+from binascii import b2a_hex
+import time
+import warnings
+
+from Crypto.pct_warnings import ClockRewindWarning
+import SHAd256
+
+import FortunaGenerator
+
+class FortunaPool(object):
+ """Fortuna pool type
+
+ This object acts like a hash object, with the following differences:
+
+ - It keeps a count (the .length attribute) of the number of bytes that
+ have been added to the pool
+ - It supports a .reset() method for in-place reinitialization
+ - The method to add bytes to the pool is .append(), not .update().
+ """
+
+ digest_size = SHAd256.digest_size
+
+ def __init__(self):
+ self.reset()
+
+ def append(self, data):
+ self._h.update(data)
+ self.length += len(data)
+
+ def digest(self):
+ return self._h.digest()
+
+ def hexdigest(self):
+ if sys.version_info[0] == 2:
+ return b2a_hex(self.digest())
+ else:
+ return b2a_hex(self.digest()).decode()
+
+ def reset(self):
+ self._h = SHAd256.new()
+ self.length = 0
+
+def which_pools(r):
+ """Return a list of pools indexes (in range(32)) that are to be included during reseed number r.
+
+ According to _Practical Cryptography_, chapter 10.5.2 "Pools":
+
+ "Pool P_i is included if 2**i is a divisor of r. Thus P_0 is used
+ every reseed, P_1 every other reseed, P_2 every fourth reseed, etc."
+ """
+ # This is a separate function so that it can be unit-tested.
+ assert r >= 1
+ retval = []
+ mask = 0
+ for i in range(32):
+ # "Pool P_i is included if 2**i is a divisor of [reseed_count]"
+ if (r & mask) == 0:
+ retval.append(i)
+ else:
+ break # optimization. once this fails, it always fails
+ mask = (mask << 1) | 1L
+ return retval
+
+class FortunaAccumulator(object):
+
+ # An estimate of how many bytes we must append to pool 0 before it will
+ # contain 128 bits of entropy (with respect to an attack). We reseed the
+ # generator only after pool 0 contains `min_pool_size` bytes. Note that
+ # unlike with some other PRNGs, Fortuna's security does not rely on the
+ # accuracy of this estimate---we can accord to be optimistic here.
+ min_pool_size = 64 # size in bytes
+
+ # If an attacker can predict some (but not all) of our entropy sources, the
+ # `min_pool_size` check may not be sufficient to prevent a successful state
+ # compromise extension attack. To resist this attack, Fortuna spreads the
+ # input across 32 pools, which are then consumed (to reseed the output
+ # generator) with exponentially decreasing frequency.
+ #
+ # In order to prevent an attacker from gaining knowledge of all 32 pools
+ # before we have a chance to fill them with enough information that the
+ # attacker cannot predict, we impose a rate limit of 10 reseeds/second (one
+ # per 100 ms). This ensures that a hypothetical 33rd pool would only be
+ # needed after a minimum of 13 years of sustained attack.
+ reseed_interval = 0.100 # time in seconds
+
+ def __init__(self):
+ self.reseed_count = 0
+ self.generator = FortunaGenerator.AESGenerator()
+ self.last_reseed = None
+
+ # Initialize 32 FortunaPool instances.
+ # NB: This is _not_ equivalent to [FortunaPool()]*32, which would give
+ # us 32 references to the _same_ FortunaPool instance (and cause the
+ # assertion below to fail).
+ self.pools = [FortunaPool() for i in range(32)] # 32 pools
+ assert(self.pools[0] is not self.pools[1])
+
+ def _forget_last_reseed(self):
+ # This is not part of the standard Fortuna definition, and using this
+ # function frequently can weaken Fortuna's ability to resist a state
+ # compromise extension attack, but we need this in order to properly
+ # implement Crypto.Random.atfork(). Otherwise, forked child processes
+ # might continue to use their parent's PRNG state for up to 100ms in
+ # some cases. (e.g. CVE-2013-1445)
+ self.last_reseed = None
+
+ def random_data(self, bytes):
+ current_time = time.time()
+ if (self.last_reseed is not None and self.last_reseed > current_time): # Avoid float comparison to None to make Py3k happy
+ warnings.warn("Clock rewind detected. Resetting last_reseed.", ClockRewindWarning)
+ self.last_reseed = None
+ if (self.pools[0].length >= self.min_pool_size and
+ (self.last_reseed is None or
+ current_time > self.last_reseed + self.reseed_interval)):
+ self._reseed(current_time)
+ # The following should fail if we haven't seeded the pool yet.
+ return self.generator.pseudo_random_data(bytes)
+
+ def _reseed(self, current_time=None):
+ if current_time is None:
+ current_time = time.time()
+ seed = []
+ self.reseed_count += 1
+ self.last_reseed = current_time
+ for i in which_pools(self.reseed_count):
+ seed.append(self.pools[i].digest())
+ self.pools[i].reset()
+
+ seed = b("").join(seed)
+ self.generator.reseed(seed)
+
+ def add_random_event(self, source_number, pool_number, data):
+ assert 1 <= len(data) <= 32
+ assert 0 <= source_number <= 255
+ assert 0 <= pool_number <= 31
+ self.pools[pool_number].append(bchr(source_number))
+ self.pools[pool_number].append(bchr(len(data)))
+ self.pools[pool_number].append(data)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/Fortuna/FortunaGenerator.py b/lib/Python/Lib/Crypto/Random/Fortuna/FortunaGenerator.py
new file mode 100644
index 000000000..723fa6306
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/Fortuna/FortunaGenerator.py
@@ -0,0 +1,132 @@
+# -*- coding: ascii -*-
+#
+# FortunaGenerator.py : Fortuna's internal PRNG
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] is 2 and sys.version_info[1] is 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+import struct
+
+from Crypto.Util.number import ceil_shift, exact_log2, exact_div
+from Crypto.Util import Counter
+from Crypto.Cipher import AES
+
+import SHAd256
+
+class AESGenerator(object):
+ """The Fortuna "generator"
+
+ This is used internally by the Fortuna PRNG to generate arbitrary amounts
+ of pseudorandom data from a smaller amount of seed data.
+
+ The output is generated by running AES-256 in counter mode and re-keying
+ after every mebibyte (2**16 blocks) of output.
+ """
+
+ block_size = AES.block_size # output block size in octets (128 bits)
+ key_size = 32 # key size in octets (256 bits)
+
+ # Because of the birthday paradox, we expect to find approximately one
+ # collision for every 2**64 blocks of output from a real random source.
+ # However, this code generates pseudorandom data by running AES in
+ # counter mode, so there will be no collisions until the counter
+ # (theoretically) wraps around at 2**128 blocks. Thus, in order to prevent
+ # Fortuna's pseudorandom output from deviating perceptibly from a true
+ # random source, Ferguson and Schneier specify a limit of 2**16 blocks
+ # without rekeying.
+ max_blocks_per_request = 2**16 # Allow no more than this number of blocks per _pseudo_random_data request
+
+ _four_kiblocks_of_zeros = b("\0") * block_size * 4096
+
+ def __init__(self):
+ self.counter = Counter.new(nbits=self.block_size*8, initial_value=0, little_endian=True)
+ self.key = None
+
+ # Set some helper constants
+ self.block_size_shift = exact_log2(self.block_size)
+ assert (1 << self.block_size_shift) == self.block_size
+
+ self.blocks_per_key = exact_div(self.key_size, self.block_size)
+ assert self.key_size == self.blocks_per_key * self.block_size
+
+ self.max_bytes_per_request = self.max_blocks_per_request * self.block_size
+
+ def reseed(self, seed):
+ if self.key is None:
+ self.key = b("\0") * self.key_size
+
+ self._set_key(SHAd256.new(self.key + seed).digest())
+ self.counter() # increment counter
+ assert len(self.key) == self.key_size
+
+ def pseudo_random_data(self, bytes):
+ assert bytes >= 0
+
+ num_full_blocks = bytes >> 20
+ remainder = bytes & ((1<<20)-1)
+
+ retval = []
+ for i in xrange(num_full_blocks):
+ retval.append(self._pseudo_random_data(1<<20))
+ retval.append(self._pseudo_random_data(remainder))
+
+ return b("").join(retval)
+
+ def _set_key(self, key):
+ self.key = key
+ self._cipher = AES.new(key, AES.MODE_CTR, counter=self.counter)
+
+ def _pseudo_random_data(self, bytes):
+ if not (0 <= bytes <= self.max_bytes_per_request):
+ raise AssertionError("You cannot ask for more than 1 MiB of data per request")
+
+ num_blocks = ceil_shift(bytes, self.block_size_shift) # num_blocks = ceil(bytes / self.block_size)
+
+ # Compute the output
+ retval = self._generate_blocks(num_blocks)[:bytes]
+
+ # Switch to a new key to avoid later compromises of this output (i.e.
+ # state compromise extension attacks)
+ self._set_key(self._generate_blocks(self.blocks_per_key))
+
+ assert len(retval) == bytes
+ assert len(self.key) == self.key_size
+
+ return retval
+
+ def _generate_blocks(self, num_blocks):
+ if self.key is None:
+ raise AssertionError("generator must be seeded before use")
+ assert 0 <= num_blocks <= self.max_blocks_per_request
+ retval = []
+ for i in xrange(num_blocks >> 12): # xrange(num_blocks / 4096)
+ retval.append(self._cipher.encrypt(self._four_kiblocks_of_zeros))
+ remaining_bytes = (num_blocks & 4095) << self.block_size_shift # (num_blocks % 4095) * self.block_size
+ retval.append(self._cipher.encrypt(self._four_kiblocks_of_zeros[:remaining_bytes]))
+ return b("").join(retval)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/Fortuna/SHAd256.py b/lib/Python/Lib/Crypto/Random/Fortuna/SHAd256.py
new file mode 100644
index 000000000..2e135c9a8
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/Fortuna/SHAd256.py
@@ -0,0 +1,98 @@
+# -*- coding: ascii -*-
+#
+# Random/Fortuna/SHAd256.py : SHA_d-256 hash function implementation
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""\
+SHA_d-256 hash function implementation.
+
+This module should comply with PEP 247.
+"""
+
+__revision__ = "$Id$"
+__all__ = ['new', 'digest_size']
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+from binascii import b2a_hex
+
+from Crypto.Hash import SHA256
+
+assert SHA256.digest_size == 32
+
+class _SHAd256(object):
+ """SHA-256, doubled.
+
+ Returns SHA-256(SHA-256(data)).
+ """
+
+ digest_size = SHA256.digest_size
+
+ _internal = object()
+
+ def __init__(self, internal_api_check, sha256_hash_obj):
+ if internal_api_check is not self._internal:
+ raise AssertionError("Do not instantiate this class directly. Use %s.new()" % (__name__,))
+ self._h = sha256_hash_obj
+
+ # PEP 247 "copy" method
+ def copy(self):
+ """Return a copy of this hashing object"""
+ return _SHAd256(SHAd256._internal, self._h.copy())
+
+ # PEP 247 "digest" method
+ def digest(self):
+ """Return the hash value of this object as a binary string"""
+ retval = SHA256.new(self._h.digest()).digest()
+ assert len(retval) == 32
+ return retval
+
+ # PEP 247 "hexdigest" method
+ def hexdigest(self):
+ """Return the hash value of this object as a (lowercase) hexadecimal string"""
+ retval = b2a_hex(self.digest())
+ assert len(retval) == 64
+ if sys.version_info[0] == 2:
+ return retval
+ else:
+ return retval.decode()
+
+ # PEP 247 "update" method
+ def update(self, data):
+ self._h.update(data)
+
+# PEP 247 module-level "digest_size" variable
+digest_size = _SHAd256.digest_size
+
+# PEP 247 module-level "new" function
+def new(data=None):
+ """Return a new SHAd256 hashing object"""
+ if not data:
+ data=b("")
+ sha = _SHAd256(_SHAd256._internal, SHA256.new(data))
+ sha.new = globals()['new']
+ return sha
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/module/__init__.py b/lib/Python/Lib/Crypto/Random/Fortuna/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/__init__.py
+++ b/lib/Python/Lib/Crypto/Random/Fortuna/__init__.py
diff --git a/lib/Python/Lib/Crypto/Random/OSRNG/__init__.py b/lib/Python/Lib/Crypto/Random/OSRNG/__init__.py
new file mode 100644
index 000000000..2fbbecbfd
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/OSRNG/__init__.py
@@ -0,0 +1,40 @@
+#
+# Random/OSRNG/__init__.py : Platform-independent OS RNG API
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Provides a platform-independent interface to the random number generators
+supplied by various operating systems."""
+
+__revision__ = "$Id$"
+
+import os
+
+if os.name == 'posix':
+ from Crypto.Random.OSRNG.posix import new
+elif os.name == 'nt':
+ from Crypto.Random.OSRNG.nt import new
+elif hasattr(os, 'urandom'):
+ from Crypto.Random.OSRNG.fallback import new
+else:
+ raise ImportError("Not implemented")
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/OSRNG/fallback.py b/lib/Python/Lib/Crypto/Random/OSRNG/fallback.py
new file mode 100644
index 000000000..5bb612607
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/OSRNG/fallback.py
@@ -0,0 +1,46 @@
+#
+# Random/OSRNG/fallback.py : Fallback entropy source for systems with os.urandom
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+
+__revision__ = "$Id$"
+__all__ = ['PythonOSURandomRNG']
+
+import os
+
+from rng_base import BaseRNG
+
+class PythonOSURandomRNG(BaseRNG):
+
+ name = "<os.urandom>"
+
+ def __init__(self):
+ self._read = os.urandom
+ BaseRNG.__init__(self)
+
+ def _close(self):
+ self._read = None
+
+def new(*args, **kwargs):
+ return PythonOSURandomRNG(*args, **kwargs)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/OSRNG/nt.py b/lib/Python/Lib/Crypto/Random/OSRNG/nt.py
new file mode 100644
index 000000000..c1c2f44e6
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/OSRNG/nt.py
@@ -0,0 +1,74 @@
+#
+# Random/OSRNG/nt.py : OS entropy source for MS Windows
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+
+__revision__ = "$Id$"
+__all__ = ['WindowsRNG']
+
+import winrandom
+from rng_base import BaseRNG
+
+class WindowsRNG(BaseRNG):
+
+ name = "<CryptGenRandom>"
+
+ def __init__(self):
+ self.__winrand = winrandom.new()
+ BaseRNG.__init__(self)
+
+ def flush(self):
+ """Work around weakness in Windows RNG.
+
+ The CryptGenRandom mechanism in some versions of Windows allows an
+ attacker to learn 128 KiB of past and future output. As a workaround,
+ this function reads 128 KiB of 'random' data from Windows and discards
+ it.
+
+ For more information about the weaknesses in CryptGenRandom, see
+ _Cryptanalysis of the Random Number Generator of the Windows Operating
+ System_, by Leo Dorrendorf and Zvi Gutterman and Benny Pinkas
+ http://eprint.iacr.org/2007/419
+ """
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+ data = self.__winrand.get_bytes(128*1024)
+ assert (len(data) == 128*1024)
+ BaseRNG.flush(self)
+
+ def _close(self):
+ self.__winrand = None
+
+ def _read(self, N):
+ # Unfortunately, research shows that CryptGenRandom doesn't provide
+ # forward secrecy and fails the next-bit test unless we apply a
+ # workaround, which we do here. See http://eprint.iacr.org/2007/419
+ # for information on the vulnerability.
+ self.flush()
+ data = self.__winrand.get_bytes(N)
+ self.flush()
+ return data
+
+def new(*args, **kwargs):
+ return WindowsRNG(*args, **kwargs)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/OSRNG/posix.py b/lib/Python/Lib/Crypto/Random/OSRNG/posix.py
new file mode 100644
index 000000000..ca6ac0509
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/OSRNG/posix.py
@@ -0,0 +1,86 @@
+#
+# Random/OSRNG/posix.py : OS entropy source for POSIX systems
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+
+__revision__ = "$Id$"
+__all__ = ['DevURandomRNG']
+
+import errno
+import os
+import stat
+
+from rng_base import BaseRNG
+from Crypto.Util.py3compat import b
+
+class DevURandomRNG(BaseRNG):
+
+ def __init__(self, devname=None):
+ if devname is None:
+ self.name = "/dev/urandom"
+ else:
+ self.name = devname
+
+ # Test that /dev/urandom is a character special device
+ f = open(self.name, "rb", 0)
+ fmode = os.fstat(f.fileno())[stat.ST_MODE]
+ if not stat.S_ISCHR(fmode):
+ f.close()
+ raise TypeError("%r is not a character special device" % (self.name,))
+
+ self.__file = f
+
+ BaseRNG.__init__(self)
+
+ def _close(self):
+ self.__file.close()
+
+ def _read(self, N):
+ # Starting with Python 3 open with buffering=0 returns a FileIO object.
+ # FileIO.read behaves like read(2) and not like fread(3) and thus we
+ # have to handle the case that read returns less data as requested here
+ # more carefully.
+ data = b("")
+ while len(data) < N:
+ try:
+ d = self.__file.read(N - len(data))
+ except IOError, e:
+ # read(2) has been interrupted by a signal; redo the read
+ if e.errno == errno.EINTR:
+ continue
+ raise
+
+ if d is None:
+ # __file is in non-blocking mode and no data is available
+ return data
+ if len(d) == 0:
+ # __file is in blocking mode and arrived at EOF
+ return data
+
+ data += d
+ return data
+
+def new(*args, **kwargs):
+ return DevURandomRNG(*args, **kwargs)
+
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/OSRNG/rng_base.py b/lib/Python/Lib/Crypto/Random/OSRNG/rng_base.py
new file mode 100644
index 000000000..54c3aa0d9
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/OSRNG/rng_base.py
@@ -0,0 +1,88 @@
+#
+# Random/OSRNG/rng_base.py : Base class for OSRNG
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+
+class BaseRNG(object):
+
+ def __init__(self):
+ self.closed = False
+ self._selftest()
+
+ def __del__(self):
+ self.close()
+
+ def _selftest(self):
+ # Test that urandom can return data
+ data = self.read(16)
+ if len(data) != 16:
+ raise AssertionError("read truncated")
+
+ # Test that we get different data every time (if we don't, the RNG is
+ # probably malfunctioning)
+ data2 = self.read(16)
+ if data == data2:
+ raise AssertionError("OS RNG returned duplicate data")
+
+ # PEP 343: Support for the "with" statement
+ def __enter__(self):
+ pass
+ def __exit__(self):
+ """PEP 343 support"""
+ self.close()
+
+ def close(self):
+ if not self.closed:
+ self._close()
+ self.closed = True
+
+ def flush(self):
+ pass
+
+ def read(self, N=-1):
+ """Return N bytes from the RNG."""
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+ if not isinstance(N, (long, int)):
+ raise TypeError("an integer is required")
+ if N < 0:
+ raise ValueError("cannot read to end of infinite stream")
+ elif N == 0:
+ return ""
+ data = self._read(N)
+ if len(data) != N:
+ raise AssertionError("%s produced truncated output (requested %d, got %d)" % (self.name, N, len(data)))
+ return data
+
+ def _close(self):
+ raise NotImplementedError("child class must implement this")
+
+ def _read(self, N):
+ raise NotImplementedError("child class must implement this")
+
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/_UserFriendlyRNG.py b/lib/Python/Lib/Crypto/Random/_UserFriendlyRNG.py
new file mode 100644
index 000000000..957e006f4
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/_UserFriendlyRNG.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+#
+# Random/_UserFriendlyRNG.py : A user-friendly random number generator
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+
+import os
+import threading
+import struct
+import time
+from math import floor
+
+from Crypto.Random import OSRNG
+from Crypto.Random.Fortuna import FortunaAccumulator
+
+class _EntropySource(object):
+ def __init__(self, accumulator, src_num):
+ self._fortuna = accumulator
+ self._src_num = src_num
+ self._pool_num = 0
+
+ def feed(self, data):
+ self._fortuna.add_random_event(self._src_num, self._pool_num, data)
+ self._pool_num = (self._pool_num + 1) & 31
+
+class _EntropyCollector(object):
+
+ def __init__(self, accumulator):
+ self._osrng = OSRNG.new()
+ self._osrng_es = _EntropySource(accumulator, 255)
+ self._time_es = _EntropySource(accumulator, 254)
+ self._clock_es = _EntropySource(accumulator, 253)
+
+ def reinit(self):
+ # Add 256 bits to each of the 32 pools, twice. (For a total of 16384
+ # bits collected from the operating system.)
+ for i in range(2):
+ block = self._osrng.read(32*32)
+ for p in range(32):
+ self._osrng_es.feed(block[p*32:(p+1)*32])
+ block = None
+ self._osrng.flush()
+
+ def collect(self):
+ # Collect 64 bits of entropy from the operating system and feed it to Fortuna.
+ self._osrng_es.feed(self._osrng.read(8))
+
+ # Add the fractional part of time.time()
+ t = time.time()
+ self._time_es.feed(struct.pack("@I", int(2**30 * (t - floor(t)))))
+
+ # Add the fractional part of time.clock()
+ t = time.clock()
+ self._clock_es.feed(struct.pack("@I", int(2**30 * (t - floor(t)))))
+
+
+class _UserFriendlyRNG(object):
+
+ def __init__(self):
+ self.closed = False
+ self._fa = FortunaAccumulator.FortunaAccumulator()
+ self._ec = _EntropyCollector(self._fa)
+ self.reinit()
+
+ def reinit(self):
+ """Initialize the random number generator and seed it with entropy from
+ the operating system.
+ """
+
+ # Save the pid (helps ensure that Crypto.Random.atfork() gets called)
+ self._pid = os.getpid()
+
+ # Collect entropy from the operating system and feed it to
+ # FortunaAccumulator
+ self._ec.reinit()
+
+ # Override FortunaAccumulator's 100ms minimum re-seed interval. This
+ # is necessary to avoid a race condition between this function and
+ # self.read(), which that can otherwise cause forked child processes to
+ # produce identical output. (e.g. CVE-2013-1445)
+ #
+ # Note that if this function can be called frequently by an attacker,
+ # (and if the bits from OSRNG are insufficiently random) it will weaken
+ # Fortuna's ability to resist a state compromise extension attack.
+ self._fa._forget_last_reseed()
+
+ def close(self):
+ self.closed = True
+ self._osrng = None
+ self._fa = None
+
+ def flush(self):
+ pass
+
+ def read(self, N):
+ """Return N bytes from the RNG."""
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+ if not isinstance(N, (long, int)):
+ raise TypeError("an integer is required")
+ if N < 0:
+ raise ValueError("cannot read to end of infinite stream")
+
+ # Collect some entropy and feed it to Fortuna
+ self._ec.collect()
+
+ # Ask Fortuna to generate some bytes
+ retval = self._fa.random_data(N)
+
+ # Check that we haven't forked in the meantime. (If we have, we don't
+ # want to use the data, because it might have been duplicated in the
+ # parent process.
+ self._check_pid()
+
+ # Return the random data.
+ return retval
+
+ def _check_pid(self):
+ # Lame fork detection to remind developers to invoke Random.atfork()
+ # after every call to os.fork(). Note that this check is not reliable,
+ # since process IDs can be reused on most operating systems.
+ #
+ # You need to do Random.atfork() in the child process after every call
+ # to os.fork() to avoid reusing PRNG state. If you want to avoid
+ # leaking PRNG state to child processes (for example, if you are using
+ # os.setuid()) then you should also invoke Random.atfork() in the
+ # *parent* process.
+ if os.getpid() != self._pid:
+ raise AssertionError("PID check failed. RNG must be re-initialized after fork(). Hint: Try Random.atfork()")
+
+
+class _LockingUserFriendlyRNG(_UserFriendlyRNG):
+ def __init__(self):
+ self._lock = threading.Lock()
+ _UserFriendlyRNG.__init__(self)
+
+ def close(self):
+ self._lock.acquire()
+ try:
+ return _UserFriendlyRNG.close(self)
+ finally:
+ self._lock.release()
+
+ def reinit(self):
+ self._lock.acquire()
+ try:
+ return _UserFriendlyRNG.reinit(self)
+ finally:
+ self._lock.release()
+
+ def read(self, bytes):
+ self._lock.acquire()
+ try:
+ return _UserFriendlyRNG.read(self, bytes)
+ finally:
+ self._lock.release()
+
+class RNGFile(object):
+ def __init__(self, singleton):
+ self.closed = False
+ self._singleton = singleton
+
+ # PEP 343: Support for the "with" statement
+ def __enter__(self):
+ """PEP 343 support"""
+ def __exit__(self):
+ """PEP 343 support"""
+ self.close()
+
+ def close(self):
+ # Don't actually close the singleton, just close this RNGFile instance.
+ self.closed = True
+ self._singleton = None
+
+ def read(self, bytes):
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+ return self._singleton.read(bytes)
+
+ def flush(self):
+ if self.closed:
+ raise ValueError("I/O operation on closed file")
+
+_singleton_lock = threading.Lock()
+_singleton = None
+def _get_singleton():
+ global _singleton
+ _singleton_lock.acquire()
+ try:
+ if _singleton is None:
+ _singleton = _LockingUserFriendlyRNG()
+ return _singleton
+ finally:
+ _singleton_lock.release()
+
+def new():
+ return RNGFile(_get_singleton())
+
+def reinit():
+ _get_singleton().reinit()
+
+def get_random_bytes(n):
+ """Return the specified number of cryptographically-strong random bytes."""
+ return _get_singleton().read(n)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/__init__.py b/lib/Python/Lib/Crypto/Random/__init__.py
new file mode 100644
index 000000000..659ffee48
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/__init__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+#
+# Random/__init__.py : PyCrypto random number generation
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+__all__ = ['new']
+
+from Crypto.Random import OSRNG
+from Crypto.Random import _UserFriendlyRNG
+
+def new(*args, **kwargs):
+ """Return a file-like object that outputs cryptographically random bytes."""
+ return _UserFriendlyRNG.new(*args, **kwargs)
+
+def atfork():
+ """Call this whenever you call os.fork()"""
+ _UserFriendlyRNG.reinit()
+
+def get_random_bytes(n):
+ """Return the specified number of cryptographically-strong random bytes."""
+ return _UserFriendlyRNG.get_random_bytes(n)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Random/random.py b/lib/Python/Lib/Crypto/Random/random.py
new file mode 100644
index 000000000..bef02e651
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Random/random.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+#
+# Random/random.py : Strong alternative for the standard 'random' module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""A cryptographically strong version of Python's standard "random" module."""
+
+__revision__ = "$Id$"
+__all__ = ['StrongRandom', 'getrandbits', 'randrange', 'randint', 'choice', 'shuffle', 'sample']
+
+from Crypto import Random
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+
+class StrongRandom(object):
+ def __init__(self, rng=None, randfunc=None):
+ if randfunc is None and rng is None:
+ self._randfunc = None
+ elif randfunc is not None and rng is None:
+ self._randfunc = randfunc
+ elif randfunc is None and rng is not None:
+ self._randfunc = rng.read
+ else:
+ raise ValueError("Cannot specify both 'rng' and 'randfunc'")
+
+ def getrandbits(self, k):
+ """Return a python long integer with k random bits."""
+ if self._randfunc is None:
+ self._randfunc = Random.new().read
+ mask = (1L << k) - 1
+ return mask & bytes_to_long(self._randfunc(ceil_div(k, 8)))
+
+ def randrange(self, *args):
+ """randrange([start,] stop[, step]):
+ Return a randomly-selected element from range(start, stop, step)."""
+ if len(args) == 3:
+ (start, stop, step) = args
+ elif len(args) == 2:
+ (start, stop) = args
+ step = 1
+ elif len(args) == 1:
+ (stop,) = args
+ start = 0
+ step = 1
+ else:
+ raise TypeError("randrange expected at most 3 arguments, got %d" % (len(args),))
+ if (not isinstance(start, (int, long))
+ or not isinstance(stop, (int, long))
+ or not isinstance(step, (int, long))):
+ raise TypeError("randrange requires integer arguments")
+ if step == 0:
+ raise ValueError("randrange step argument must not be zero")
+
+ num_choices = ceil_div(stop - start, step)
+ if num_choices < 0:
+ num_choices = 0
+ if num_choices < 1:
+ raise ValueError("empty range for randrange(%r, %r, %r)" % (start, stop, step))
+
+ # Pick a random number in the range of possible numbers
+ r = num_choices
+ while r >= num_choices:
+ r = self.getrandbits(size(num_choices))
+
+ return start + (step * r)
+
+ def randint(self, a, b):
+ """Return a random integer N such that a <= N <= b."""
+ if not isinstance(a, (int, long)) or not isinstance(b, (int, long)):
+ raise TypeError("randint requires integer arguments")
+ N = self.randrange(a, b+1)
+ assert a <= N <= b
+ return N
+
+ def choice(self, seq):
+ """Return a random element from a (non-empty) sequence.
+
+ If the seqence is empty, raises IndexError.
+ """
+ if len(seq) == 0:
+ raise IndexError("empty sequence")
+ return seq[self.randrange(len(seq))]
+
+ def shuffle(self, x):
+ """Shuffle the sequence in place."""
+ # Make a (copy) of the list of objects we want to shuffle
+ items = list(x)
+
+ # Choose a random item (without replacement) until all the items have been
+ # chosen.
+ for i in xrange(len(x)):
+ x[i] = items.pop(self.randrange(len(items)))
+
+ def sample(self, population, k):
+ """Return a k-length list of unique elements chosen from the population sequence."""
+
+ num_choices = len(population)
+ if k > num_choices:
+ raise ValueError("sample larger than population")
+
+ retval = []
+ selected = {} # we emulate a set using a dict here
+ for i in xrange(k):
+ r = None
+ while r is None or selected.has_key(r):
+ r = self.randrange(num_choices)
+ retval.append(population[r])
+ selected[r] = 1
+ return retval
+
+_r = StrongRandom()
+getrandbits = _r.getrandbits
+randrange = _r.randrange
+randint = _r.randint
+choice = _r.choice
+shuffle = _r.shuffle
+sample = _r.sample
+
+# These are at the bottom to avoid problems with recursive imports
+from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes, size
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/__init__.py
new file mode 100644
index 000000000..63e9c5706
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/__init__.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/__init__.py: Self-test for cipher modules
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for cipher modules"""
+
+__revision__ = "$Id$"
+
+def get_tests(config={}):
+ tests = []
+ from Crypto.SelfTest.Cipher import test_AES; tests += test_AES.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_ARC2; tests += test_ARC2.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_ARC4; tests += test_ARC4.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_Blowfish; tests += test_Blowfish.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_CAST; tests += test_CAST.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_DES3; tests += test_DES3.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_DES; tests += test_DES.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_XOR; tests += test_XOR.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_pkcs1_15; tests += test_pkcs1_15.get_tests(config=config)
+ from Crypto.SelfTest.Cipher import test_pkcs1_oaep; tests += test_pkcs1_oaep.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/common.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/common.py
new file mode 100644
index 000000000..8bebed9cf
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/common.py
@@ -0,0 +1,399 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/common.py: Common code for Crypto.SelfTest.Hash
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-testing for PyCrypto hash modules"""
+
+__revision__ = "$Id$"
+
+import sys
+import unittest
+from binascii import a2b_hex, b2a_hex
+from Crypto.Util.py3compat import *
+
+# For compatibility with Python 2.1 and Python 2.2
+if sys.hexversion < 0x02030000:
+ # Python 2.1 doesn't have a dict() function
+ # Python 2.2 dict() function raises TypeError if you do dict(MD5='blah')
+ def dict(**kwargs):
+ return kwargs.copy()
+else:
+ dict = dict
+
+class _NoDefault: pass # sentinel object
+def _extract(d, k, default=_NoDefault):
+ """Get an item from a dictionary, and remove it from the dictionary."""
+ try:
+ retval = d[k]
+ except KeyError:
+ if default is _NoDefault:
+ raise
+ return default
+ del d[k]
+ return retval
+
+# Generic cipher test case
+class CipherSelfTest(unittest.TestCase):
+
+ def __init__(self, module, params):
+ unittest.TestCase.__init__(self)
+ self.module = module
+
+ # Extract the parameters
+ params = params.copy()
+ self.description = _extract(params, 'description')
+ self.key = b(_extract(params, 'key'))
+ self.plaintext = b(_extract(params, 'plaintext'))
+ self.ciphertext = b(_extract(params, 'ciphertext'))
+ self.module_name = _extract(params, 'module_name', None)
+
+ mode = _extract(params, 'mode', None)
+ self.mode_name = str(mode)
+ if mode is not None:
+ # Block cipher
+ self.mode = getattr(self.module, "MODE_" + mode)
+ self.iv = _extract(params, 'iv', None)
+ if self.iv is not None: self.iv = b(self.iv)
+
+ # Only relevant for OPENPGP mode
+ self.encrypted_iv = _extract(params, 'encrypted_iv', None)
+ if self.encrypted_iv is not None:
+ self.encrypted_iv = b(self.encrypted_iv)
+ else:
+ # Stream cipher
+ self.mode = None
+ self.iv = None
+
+ self.extra_params = params
+
+ def shortDescription(self):
+ return self.description
+
+ def _new(self, do_decryption=0):
+ params = self.extra_params.copy()
+
+ # Handle CTR mode parameters. By default, we use Counter.new(self.module.block_size)
+ if hasattr(self.module, "MODE_CTR") and self.mode == self.module.MODE_CTR:
+ from Crypto.Util import Counter
+ ctr_class = _extract(params, 'ctr_class', Counter.new)
+ ctr_params = _extract(params, 'ctr_params', {}).copy()
+ if ctr_params.has_key('prefix'): ctr_params['prefix'] = a2b_hex(b(ctr_params['prefix']))
+ if ctr_params.has_key('suffix'): ctr_params['suffix'] = a2b_hex(b(ctr_params['suffix']))
+ if not ctr_params.has_key('nbits'):
+ ctr_params['nbits'] = 8*(self.module.block_size - len(ctr_params.get('prefix', '')) - len(ctr_params.get('suffix', '')))
+ params['counter'] = ctr_class(**ctr_params)
+
+ if self.mode is None:
+ # Stream cipher
+ return self.module.new(a2b_hex(self.key), **params)
+ elif self.iv is None:
+ # Block cipher without iv
+ return self.module.new(a2b_hex(self.key), self.mode, **params)
+ else:
+ # Block cipher with iv
+ if do_decryption and self.mode == self.module.MODE_OPENPGP:
+ # In PGP mode, the IV to feed for decryption is the *encrypted* one
+ return self.module.new(a2b_hex(self.key), self.mode, a2b_hex(self.encrypted_iv), **params)
+ else:
+ return self.module.new(a2b_hex(self.key), self.mode, a2b_hex(self.iv), **params)
+
+ def runTest(self):
+ plaintext = a2b_hex(self.plaintext)
+ ciphertext = a2b_hex(self.ciphertext)
+
+ ct1 = b2a_hex(self._new().encrypt(plaintext))
+ pt1 = b2a_hex(self._new(1).decrypt(ciphertext))
+ ct2 = b2a_hex(self._new().encrypt(plaintext))
+ pt2 = b2a_hex(self._new(1).decrypt(ciphertext))
+
+ if hasattr(self.module, "MODE_OPENPGP") and self.mode == self.module.MODE_OPENPGP:
+ # In PGP mode, data returned by the first encrypt()
+ # is prefixed with the encrypted IV.
+ # Here we check it and then remove it from the ciphertexts.
+ eilen = len(self.encrypted_iv)
+ self.assertEqual(self.encrypted_iv, ct1[:eilen])
+ self.assertEqual(self.encrypted_iv, ct2[:eilen])
+ ct1 = ct1[eilen:]
+ ct2 = ct2[eilen:]
+
+ self.assertEqual(self.ciphertext, ct1) # encrypt
+ self.assertEqual(self.ciphertext, ct2) # encrypt (second time)
+ self.assertEqual(self.plaintext, pt1) # decrypt
+ self.assertEqual(self.plaintext, pt2) # decrypt (second time)
+
+class CipherStreamingSelfTest(CipherSelfTest):
+
+ def shortDescription(self):
+ desc = self.module_name
+ if self.mode is not None:
+ desc += " in %s mode" % (self.mode_name,)
+ return "%s should behave like a stream cipher" % (desc,)
+
+ def runTest(self):
+ plaintext = a2b_hex(self.plaintext)
+ ciphertext = a2b_hex(self.ciphertext)
+
+ # The cipher should work like a stream cipher
+
+ # Test counter mode encryption, 3 bytes at a time
+ ct3 = []
+ cipher = self._new()
+ for i in range(0, len(plaintext), 3):
+ ct3.append(cipher.encrypt(plaintext[i:i+3]))
+ ct3 = b2a_hex(b("").join(ct3))
+ self.assertEqual(self.ciphertext, ct3) # encryption (3 bytes at a time)
+
+ # Test counter mode decryption, 3 bytes at a time
+ pt3 = []
+ cipher = self._new()
+ for i in range(0, len(ciphertext), 3):
+ pt3.append(cipher.encrypt(ciphertext[i:i+3]))
+ # PY3K: This is meant to be text, do not change to bytes (data)
+ pt3 = b2a_hex(b("").join(pt3))
+ self.assertEqual(self.plaintext, pt3) # decryption (3 bytes at a time)
+
+class CTRSegfaultTest(unittest.TestCase):
+
+ def __init__(self, module, params):
+ unittest.TestCase.__init__(self)
+ self.module = module
+ self.key = b(params['key'])
+ self.module_name = params.get('module_name', None)
+
+ def shortDescription(self):
+ return """Regression test: %s.new(key, %s.MODE_CTR) should raise TypeError, not segfault""" % (self.module_name, self.module_name)
+
+ def runTest(self):
+ self.assertRaises(TypeError, self.module.new, a2b_hex(self.key), self.module.MODE_CTR)
+
+class CTRWraparoundTest(unittest.TestCase):
+
+ def __init__(self, module, params):
+ unittest.TestCase.__init__(self)
+ self.module = module
+ self.key = b(params['key'])
+ self.module_name = params.get('module_name', None)
+
+ def shortDescription(self):
+ return """Regression test: %s with MODE_CTR should raise OverflowError on wraparound when shortcut used""" % (self.module_name,)
+
+ def runTest(self):
+ from Crypto.Util import Counter
+
+ for disable_shortcut in (0, 1): # (False, True) Test CTR-mode shortcut and PyObject_CallObject code paths
+ for little_endian in (0, 1): # (False, True) Test both endiannesses
+ ctr = Counter.new(8*self.module.block_size, initial_value=2L**(8*self.module.block_size)-1, little_endian=little_endian, disable_shortcut=disable_shortcut)
+ cipher = self.module.new(a2b_hex(self.key), self.module.MODE_CTR, counter=ctr)
+ block = b("\x00") * self.module.block_size
+ cipher.encrypt(block)
+ self.assertRaises(OverflowError, cipher.encrypt, block)
+
+class CFBSegmentSizeTest(unittest.TestCase):
+
+ def __init__(self, module, params):
+ unittest.TestCase.__init__(self)
+ self.module = module
+ self.key = b(params['key'])
+ self.description = params['description']
+
+ def shortDescription(self):
+ return self.description
+
+ def runTest(self):
+ """Regression test: m.new(key, m.MODE_CFB, segment_size=N) should require segment_size to be a multiple of 8 bits"""
+ for i in range(1, 8):
+ self.assertRaises(ValueError, self.module.new, a2b_hex(self.key), self.module.MODE_CFB, segment_size=i)
+ self.module.new(a2b_hex(self.key), self.module.MODE_CFB, "\0"*self.module.block_size, segment_size=8) # should succeed
+
+class RoundtripTest(unittest.TestCase):
+ def __init__(self, module, params):
+ from Crypto import Random
+ unittest.TestCase.__init__(self)
+ self.module = module
+ self.iv = Random.get_random_bytes(module.block_size)
+ self.key = b(params['key'])
+ self.plaintext = 100 * b(params['plaintext'])
+ self.module_name = params.get('module_name', None)
+
+ def shortDescription(self):
+ return """%s .decrypt() output of .encrypt() should not be garbled""" % (self.module_name,)
+
+ def runTest(self):
+ for mode in (self.module.MODE_ECB, self.module.MODE_CBC, self.module.MODE_CFB, self.module.MODE_OFB, self.module.MODE_OPENPGP):
+ encryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv)
+ ciphertext = encryption_cipher.encrypt(self.plaintext)
+
+ if mode != self.module.MODE_OPENPGP:
+ decryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv)
+ else:
+ eiv = ciphertext[:self.module.block_size+2]
+ ciphertext = ciphertext[self.module.block_size+2:]
+ decryption_cipher = self.module.new(a2b_hex(self.key), mode, eiv)
+ decrypted_plaintext = decryption_cipher.decrypt(ciphertext)
+ self.assertEqual(self.plaintext, decrypted_plaintext)
+
+class PGPTest(unittest.TestCase):
+ def __init__(self, module, params):
+ unittest.TestCase.__init__(self)
+ self.module = module
+ self.key = b(params['key'])
+
+ def shortDescription(self):
+ return "MODE_PGP was implemented incorrectly and insecurely. It's completely banished now."
+
+ def runTest(self):
+ self.assertRaises(ValueError, self.module.new, a2b_hex(self.key),
+ self.module.MODE_PGP)
+
+class IVLengthTest(unittest.TestCase):
+ def __init__(self, module, params):
+ unittest.TestCase.__init__(self)
+ self.module = module
+ self.key = b(params['key'])
+
+ def shortDescription(self):
+ return "Check that all modes except MODE_ECB and MODE_CTR require an IV of the proper length"
+
+ def runTest(self):
+ self.assertRaises(ValueError, self.module.new, a2b_hex(self.key),
+ self.module.MODE_CBC, "")
+ self.assertRaises(ValueError, self.module.new, a2b_hex(self.key),
+ self.module.MODE_CFB, "")
+ self.assertRaises(ValueError, self.module.new, a2b_hex(self.key),
+ self.module.MODE_OFB, "")
+ self.assertRaises(ValueError, self.module.new, a2b_hex(self.key),
+ self.module.MODE_OPENPGP, "")
+ self.module.new(a2b_hex(self.key), self.module.MODE_ECB, "")
+ self.module.new(a2b_hex(self.key), self.module.MODE_CTR, "", counter=self._dummy_counter)
+
+ def _dummy_counter(self):
+ return "\0" * self.module.block_size
+
+def make_block_tests(module, module_name, test_data):
+ tests = []
+ extra_tests_added = 0
+ for i in range(len(test_data)):
+ row = test_data[i]
+
+ # Build the "params" dictionary
+ params = {'mode': 'ECB'}
+ if len(row) == 3:
+ (params['plaintext'], params['ciphertext'], params['key']) = row
+ elif len(row) == 4:
+ (params['plaintext'], params['ciphertext'], params['key'], params['description']) = row
+ elif len(row) == 5:
+ (params['plaintext'], params['ciphertext'], params['key'], params['description'], extra_params) = row
+ params.update(extra_params)
+ else:
+ raise AssertionError("Unsupported tuple size %d" % (len(row),))
+
+ # Build the display-name for the test
+ p2 = params.copy()
+ p_key = _extract(p2, 'key')
+ p_plaintext = _extract(p2, 'plaintext')
+ p_ciphertext = _extract(p2, 'ciphertext')
+ p_description = _extract(p2, 'description', None)
+ p_mode = p2.get('mode', 'ECB')
+ if p_mode == 'ECB':
+ _extract(p2, 'mode', 'ECB')
+
+ if p_description is not None:
+ description = p_description
+ elif p_mode == 'ECB' and not p2:
+ description = "p=%s, k=%s" % (p_plaintext, p_key)
+ else:
+ description = "p=%s, k=%s, %r" % (p_plaintext, p_key, p2)
+ name = "%s #%d: %s" % (module_name, i+1, description)
+ params['description'] = name
+ params['module_name'] = module_name
+
+ # Add extra test(s) to the test suite before the current test
+ if not extra_tests_added:
+ tests += [
+ CTRSegfaultTest(module, params),
+ CTRWraparoundTest(module, params),
+ CFBSegmentSizeTest(module, params),
+ RoundtripTest(module, params),
+ PGPTest(module, params),
+ IVLengthTest(module, params),
+ ]
+ extra_tests_added = 1
+
+ # Add the current test to the test suite
+ tests.append(CipherSelfTest(module, params))
+
+ # When using CTR mode, test that the interface behaves like a stream cipher
+ if p_mode == 'CTR':
+ tests.append(CipherStreamingSelfTest(module, params))
+
+ # When using CTR mode, test the non-shortcut code path.
+ if p_mode == 'CTR' and not params.has_key('ctr_class'):
+ params2 = params.copy()
+ params2['description'] += " (shortcut disabled)"
+ ctr_params2 = params.get('ctr_params', {}).copy()
+ params2['ctr_params'] = ctr_params2
+ if not params2['ctr_params'].has_key('disable_shortcut'):
+ params2['ctr_params']['disable_shortcut'] = 1
+ tests.append(CipherSelfTest(module, params2))
+ return tests
+
+def make_stream_tests(module, module_name, test_data):
+ tests = []
+ for i in range(len(test_data)):
+ row = test_data[i]
+
+ # Build the "params" dictionary
+ params = {}
+ if len(row) == 3:
+ (params['plaintext'], params['ciphertext'], params['key']) = row
+ elif len(row) == 4:
+ (params['plaintext'], params['ciphertext'], params['key'], params['description']) = row
+ elif len(row) == 5:
+ (params['plaintext'], params['ciphertext'], params['key'], params['description'], extra_params) = row
+ params.update(extra_params)
+ else:
+ raise AssertionError("Unsupported tuple size %d" % (len(row),))
+
+ # Build the display-name for the test
+ p2 = params.copy()
+ p_key = _extract(p2, 'key')
+ p_plaintext = _extract(p2, 'plaintext')
+ p_ciphertext = _extract(p2, 'ciphertext')
+ p_description = _extract(p2, 'description', None)
+
+ if p_description is not None:
+ description = p_description
+ elif not p2:
+ description = "p=%s, k=%s" % (p_plaintext, p_key)
+ else:
+ description = "p=%s, k=%s, %r" % (p_plaintext, p_key, p2)
+ name = "%s #%d: %s" % (module_name, i+1, description)
+ params['description'] = name
+ params['module_name'] = module_name
+
+ # Add the test to the test suite
+ tests.append(CipherSelfTest(module, params))
+ tests.append(CipherStreamingSelfTest(module, params))
+ return tests
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_AES.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_AES.py
new file mode 100644
index 000000000..ea7c323a6
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_AES.py
@@ -0,0 +1,1433 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/AES.py: Self-test for the AES cipher
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.AES"""
+
+__revision__ = "$Id$"
+
+from common import dict # For compatibility with Python 2.1 and 2.2
+from Crypto.Util.py3compat import *
+from binascii import hexlify
+
+# This is a list of (plaintext, ciphertext, key[, description[, params]]) tuples.
+test_data = [
+ # FIPS PUB 197 test vectors
+ # http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
+
+ ('00112233445566778899aabbccddeeff', '69c4e0d86a7b0430d8cdb78070b4c55a',
+ '000102030405060708090a0b0c0d0e0f', 'FIPS 197 C.1 (AES-128)'),
+
+ ('00112233445566778899aabbccddeeff', 'dda97ca4864cdfe06eaf70a0ec0d7191',
+ '000102030405060708090a0b0c0d0e0f1011121314151617',
+ 'FIPS 197 C.2 (AES-192)'),
+
+ ('00112233445566778899aabbccddeeff', '8ea2b7ca516745bfeafc49904b496089',
+ '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+ 'FIPS 197 C.3 (AES-256)'),
+
+ # Rijndael128 test vectors
+ # Downloaded 2008-09-13 from
+ # http://www.iaik.tugraz.at/Research/krypto/AES/old/~rijmen/rijndael/testvalues.tar.gz
+
+ # ecb_tbl.txt, KEYSIZE=128
+ ('506812a45f08c889b97f5980038b8359', 'd8f532538289ef7d06b506a4fd5be9c9',
+ '00010203050607080a0b0c0d0f101112',
+ 'ecb-tbl-128: I=1'),
+ ('5c6d71ca30de8b8b00549984d2ec7d4b', '59ab30f4d4ee6e4ff9907ef65b1fb68c',
+ '14151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-128: I=2'),
+ ('53f3f4c64f8616e4e7c56199f48f21f6', 'bf1ed2fcb2af3fd41443b56d85025cb1',
+ '28292a2b2d2e2f30323334353738393a',
+ 'ecb-tbl-128: I=3'),
+ ('a1eb65a3487165fb0f1c27ff9959f703', '7316632d5c32233edcb0780560eae8b2',
+ '3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-128: I=4'),
+ ('3553ecf0b1739558b08e350a98a39bfa', '408c073e3e2538072b72625e68b8364b',
+ '50515253555657585a5b5c5d5f606162',
+ 'ecb-tbl-128: I=5'),
+ ('67429969490b9711ae2b01dc497afde8', 'e1f94dfa776597beaca262f2f6366fea',
+ '64656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-128: I=6'),
+ ('93385c1f2aec8bed192f5a8e161dd508', 'f29e986c6a1c27d7b29ffd7ee92b75f1',
+ '78797a7b7d7e7f80828384858788898a',
+ 'ecb-tbl-128: I=7'),
+ ('b5bf946be19beb8db3983b5f4c6e8ddb', '131c886a57f8c2e713aba6955e2b55b5',
+ '8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-128: I=8'),
+ ('41321ee10e21bd907227c4450ff42324', 'd2ab7662df9b8c740210e5eeb61c199d',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2',
+ 'ecb-tbl-128: I=9'),
+ ('00a82f59c91c8486d12c0a80124f6089', '14c10554b2859c484cab5869bbe7c470',
+ 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6',
+ 'ecb-tbl-128: I=10'),
+ ('7ce0fd076754691b4bbd9faf8a1372fe', 'db4d498f0a49cf55445d502c1f9ab3b5',
+ 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9da',
+ 'ecb-tbl-128: I=11'),
+ ('23605a8243d07764541bc5ad355b3129', '6d96fef7d66590a77a77bb2056667f7f',
+ 'dcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-128: I=12'),
+ ('12a8cfa23ea764fd876232b4e842bc44', '316fb68edba736c53e78477bf913725c',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe010002',
+ 'ecb-tbl-128: I=13'),
+ ('bcaf32415e8308b3723e5fdd853ccc80', '6936f2b93af8397fd3a771fc011c8c37',
+ '04050607090a0b0c0e0f101113141516',
+ 'ecb-tbl-128: I=14'),
+ ('89afae685d801ad747ace91fc49adde0', 'f3f92f7a9c59179c1fcc2c2ba0b082cd',
+ '2c2d2e2f31323334363738393b3c3d3e',
+ 'ecb-tbl-128: I=15'),
+ ('f521d07b484357c4a69e76124a634216', '6a95ea659ee3889158e7a9152ff04ebc',
+ '40414243454647484a4b4c4d4f505152',
+ 'ecb-tbl-128: I=16'),
+ ('3e23b3bc065bcc152407e23896d77783', '1959338344e945670678a5d432c90b93',
+ '54555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-128: I=17'),
+ ('79f0fba002be1744670e7e99290d8f52', 'e49bddd2369b83ee66e6c75a1161b394',
+ '68696a6b6d6e6f70727374757778797a',
+ 'ecb-tbl-128: I=18'),
+ ('da23fe9d5bd63e1d72e3dafbe21a6c2a', 'd3388f19057ff704b70784164a74867d',
+ '7c7d7e7f81828384868788898b8c8d8e',
+ 'ecb-tbl-128: I=19'),
+ ('e3f5698ba90b6a022efd7db2c7e6c823', '23aa03e2d5e4cd24f3217e596480d1e1',
+ 'a4a5a6a7a9aaabacaeafb0b1b3b4b5b6',
+ 'ecb-tbl-128: I=20'),
+ ('bdc2691d4f1b73d2700679c3bcbf9c6e', 'c84113d68b666ab2a50a8bdb222e91b9',
+ 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2',
+ 'ecb-tbl-128: I=21'),
+ ('ba74e02093217ee1ba1b42bd5624349a', 'ac02403981cd4340b507963db65cb7b6',
+ '08090a0b0d0e0f10121314151718191a',
+ 'ecb-tbl-128: I=22'),
+ ('b5c593b5851c57fbf8b3f57715e8f680', '8d1299236223359474011f6bf5088414',
+ '6c6d6e6f71727374767778797b7c7d7e',
+ 'ecb-tbl-128: I=23'),
+ ('3da9bd9cec072381788f9387c3bbf4ee', '5a1d6ab8605505f7977e55b9a54d9b90',
+ '80818283858687888a8b8c8d8f909192',
+ 'ecb-tbl-128: I=24'),
+ ('4197f3051121702ab65d316b3c637374', '72e9c2d519cf555e4208805aabe3b258',
+ '94959697999a9b9c9e9fa0a1a3a4a5a6',
+ 'ecb-tbl-128: I=25'),
+ ('9f46c62ec4f6ee3f6e8c62554bc48ab7', 'a8f3e81c4a23a39ef4d745dffe026e80',
+ 'a8a9aaabadaeafb0b2b3b4b5b7b8b9ba',
+ 'ecb-tbl-128: I=26'),
+ ('0220673fe9e699a4ebc8e0dbeb6979c8', '546f646449d31458f9eb4ef5483aee6c',
+ 'bcbdbebfc1c2c3c4c6c7c8c9cbcccdce',
+ 'ecb-tbl-128: I=27'),
+ ('b2b99171337ded9bc8c2c23ff6f18867', '4dbe4bc84ac797c0ee4efb7f1a07401c',
+ 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2',
+ 'ecb-tbl-128: I=28'),
+ ('a7facf4e301e984e5efeefd645b23505', '25e10bfb411bbd4d625ac8795c8ca3b3',
+ 'e4e5e6e7e9eaebeceeeff0f1f3f4f5f6',
+ 'ecb-tbl-128: I=29'),
+ ('f7c762e4a9819160fd7acfb6c4eedcdd', '315637405054ec803614e43def177579',
+ 'f8f9fafbfdfefe00020304050708090a',
+ 'ecb-tbl-128: I=30'),
+ ('9b64fc21ea08709f4915436faa70f1be', '60c5bc8a1410247295c6386c59e572a8',
+ '0c0d0e0f11121314161718191b1c1d1e',
+ 'ecb-tbl-128: I=31'),
+ ('52af2c3de07ee6777f55a4abfc100b3f', '01366fc8ca52dfe055d6a00a76471ba6',
+ '20212223252627282a2b2c2d2f303132',
+ 'ecb-tbl-128: I=32'),
+ ('2fca001224386c57aa3f968cbe2c816f', 'ecc46595516ec612449c3f581e7d42ff',
+ '34353637393a3b3c3e3f404143444546',
+ 'ecb-tbl-128: I=33'),
+ ('4149c73658a4a9c564342755ee2c132f', '6b7ffe4c602a154b06ee9c7dab5331c9',
+ '48494a4b4d4e4f50525354555758595a',
+ 'ecb-tbl-128: I=34'),
+ ('af60005a00a1772f7c07a48a923c23d2', '7da234c14039a240dd02dd0fbf84eb67',
+ '5c5d5e5f61626364666768696b6c6d6e',
+ 'ecb-tbl-128: I=35'),
+ ('6fccbc28363759914b6f0280afaf20c6', 'c7dc217d9e3604ffe7e91f080ecd5a3a',
+ '70717273757677787a7b7c7d7f808182',
+ 'ecb-tbl-128: I=36'),
+ ('7d82a43ddf4fefa2fc5947499884d386', '37785901863f5c81260ea41e7580cda5',
+ '84858687898a8b8c8e8f909193949596',
+ 'ecb-tbl-128: I=37'),
+ ('5d5a990eaab9093afe4ce254dfa49ef9', 'a07b9338e92ed105e6ad720fccce9fe4',
+ '98999a9b9d9e9fa0a2a3a4a5a7a8a9aa',
+ 'ecb-tbl-128: I=38'),
+ ('4cd1e2fd3f4434b553aae453f0ed1a02', 'ae0fb9722418cc21a7da816bbc61322c',
+ 'acadaeafb1b2b3b4b6b7b8b9bbbcbdbe',
+ 'ecb-tbl-128: I=39'),
+ ('5a2c9a9641d4299125fa1b9363104b5e', 'c826a193080ff91ffb21f71d3373c877',
+ 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2',
+ 'ecb-tbl-128: I=40'),
+ ('b517fe34c0fa217d341740bfd4fe8dd4', '1181b11b0e494e8d8b0aa6b1d5ac2c48',
+ 'd4d5d6d7d9dadbdcdedfe0e1e3e4e5e6',
+ 'ecb-tbl-128: I=41'),
+ ('014baf2278a69d331d5180103643e99a', '6743c3d1519ab4f2cd9a78ab09a511bd',
+ 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fa',
+ 'ecb-tbl-128: I=42'),
+ ('b529bd8164f20d0aa443d4932116841c', 'dc55c076d52bacdf2eefd952946a439d',
+ 'fcfdfeff01020304060708090b0c0d0e',
+ 'ecb-tbl-128: I=43'),
+ ('2e596dcbb2f33d4216a1176d5bd1e456', '711b17b590ffc72b5c8e342b601e8003',
+ '10111213151617181a1b1c1d1f202122',
+ 'ecb-tbl-128: I=44'),
+ ('7274a1ea2b7ee2424e9a0e4673689143', '19983bb0950783a537e1339f4aa21c75',
+ '24252627292a2b2c2e2f303133343536',
+ 'ecb-tbl-128: I=45'),
+ ('ae20020bd4f13e9d90140bee3b5d26af', '3ba7762e15554169c0f4fa39164c410c',
+ '38393a3b3d3e3f40424344454748494a',
+ 'ecb-tbl-128: I=46'),
+ ('baac065da7ac26e855e79c8849d75a02', 'a0564c41245afca7af8aa2e0e588ea89',
+ '4c4d4e4f51525354565758595b5c5d5e',
+ 'ecb-tbl-128: I=47'),
+ ('7c917d8d1d45fab9e2540e28832540cc', '5e36a42a2e099f54ae85ecd92e2381ed',
+ '60616263656667686a6b6c6d6f707172',
+ 'ecb-tbl-128: I=48'),
+ ('bde6f89e16daadb0e847a2a614566a91', '770036f878cd0f6ca2268172f106f2fe',
+ '74757677797a7b7c7e7f808183848586',
+ 'ecb-tbl-128: I=49'),
+ ('c9de163725f1f5be44ebb1db51d07fbc', '7e4e03908b716116443ccf7c94e7c259',
+ '88898a8b8d8e8f90929394959798999a',
+ 'ecb-tbl-128: I=50'),
+ ('3af57a58f0c07dffa669572b521e2b92', '482735a48c30613a242dd494c7f9185d',
+ '9c9d9e9fa1a2a3a4a6a7a8a9abacadae',
+ 'ecb-tbl-128: I=51'),
+ ('3d5ebac306dde4604f1b4fbbbfcdae55', 'b4c0f6c9d4d7079addf9369fc081061d',
+ 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2',
+ 'ecb-tbl-128: I=52'),
+ ('c2dfa91bceb76a1183c995020ac0b556', 'd5810fe0509ac53edcd74f89962e6270',
+ 'c4c5c6c7c9cacbcccecfd0d1d3d4d5d6',
+ 'ecb-tbl-128: I=53'),
+ ('c70f54305885e9a0746d01ec56c8596b', '03f17a16b3f91848269ecdd38ebb2165',
+ 'd8d9dadbdddedfe0e2e3e4e5e7e8e9ea',
+ 'ecb-tbl-128: I=54'),
+ ('c4f81b610e98012ce000182050c0c2b2', 'da1248c3180348bad4a93b4d9856c9df',
+ 'ecedeeeff1f2f3f4f6f7f8f9fbfcfdfe',
+ 'ecb-tbl-128: I=55'),
+ ('eaab86b1d02a95d7404eff67489f97d4', '3d10d7b63f3452c06cdf6cce18be0c2c',
+ '00010203050607080a0b0c0d0f101112',
+ 'ecb-tbl-128: I=56'),
+ ('7c55bdb40b88870b52bec3738de82886', '4ab823e7477dfddc0e6789018fcb6258',
+ '14151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-128: I=57'),
+ ('ba6eaa88371ff0a3bd875e3f2a975ce0', 'e6478ba56a77e70cfdaa5c843abde30e',
+ '28292a2b2d2e2f30323334353738393a',
+ 'ecb-tbl-128: I=58'),
+ ('08059130c4c24bd30cf0575e4e0373dc', '1673064895fbeaf7f09c5429ff75772d',
+ '3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-128: I=59'),
+ ('9a8eab004ef53093dfcf96f57e7eda82', '4488033ae9f2efd0ca9383bfca1a94e9',
+ '50515253555657585a5b5c5d5f606162',
+ 'ecb-tbl-128: I=60'),
+ ('0745b589e2400c25f117b1d796c28129', '978f3b8c8f9d6f46626cac3c0bcb9217',
+ '64656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-128: I=61'),
+ ('2f1777781216cec3f044f134b1b92bbe', 'e08c8a7e582e15e5527f1d9e2eecb236',
+ '78797a7b7d7e7f80828384858788898a',
+ 'ecb-tbl-128: I=62'),
+ ('353a779ffc541b3a3805d90ce17580fc', 'cec155b76ac5ffda4cf4f9ca91e49a7a',
+ '8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-128: I=63'),
+ ('1a1eae4415cefcf08c4ac1c8f68bea8f', 'd5ac7165763225dd2a38cdc6862c29ad',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2',
+ 'ecb-tbl-128: I=64'),
+ ('e6e7e4e5b0b3b2b5d4d5aaab16111013', '03680fe19f7ce7275452020be70e8204',
+ 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6',
+ 'ecb-tbl-128: I=65'),
+ ('f8f9fafbfbf8f9e677767170efe0e1e2', '461df740c9781c388e94bb861ceb54f6',
+ 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9da',
+ 'ecb-tbl-128: I=66'),
+ ('63626160a1a2a3a445444b4a75727370', '451bd60367f96483042742219786a074',
+ 'dcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-128: I=67'),
+ ('717073720605040b2d2c2b2a05fafbf9', 'e4dfa42671a02e57ef173b85c0ea9f2b',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe010002',
+ 'ecb-tbl-128: I=68'),
+ ('78797a7beae9e8ef3736292891969794', 'ed11b89e76274282227d854700a78b9e',
+ '04050607090a0b0c0e0f101113141516',
+ 'ecb-tbl-128: I=69'),
+ ('838281803231300fdddcdbdaa0afaead', '433946eaa51ea47af33895f2b90b3b75',
+ '18191a1b1d1e1f20222324252728292a',
+ 'ecb-tbl-128: I=70'),
+ ('18191a1bbfbcbdba75747b7a7f78797a', '6bc6d616a5d7d0284a5910ab35022528',
+ '2c2d2e2f31323334363738393b3c3d3e',
+ 'ecb-tbl-128: I=71'),
+ ('848586879b989996a3a2a5a4849b9a99', 'd2a920ecfe919d354b5f49eae9719c98',
+ '40414243454647484a4b4c4d4f505152',
+ 'ecb-tbl-128: I=72'),
+ ('0001020322212027cacbf4f551565754', '3a061b17f6a92885efbd0676985b373d',
+ '54555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-128: I=73'),
+ ('cecfcccdafacadb2515057564a454447', 'fadeec16e33ea2f4688499d157e20d8f',
+ '68696a6b6d6e6f70727374757778797a',
+ 'ecb-tbl-128: I=74'),
+ ('92939091cdcecfc813121d1c80878685', '5cdefede59601aa3c3cda36fa6b1fa13',
+ '7c7d7e7f81828384868788898b8c8d8e',
+ 'ecb-tbl-128: I=75'),
+ ('d2d3d0d16f6c6d6259585f5ed1eeefec', '9574b00039844d92ebba7ee8719265f8',
+ '90919293959697989a9b9c9d9fa0a1a2',
+ 'ecb-tbl-128: I=76'),
+ ('acadaeaf878485820f0e1110d5d2d3d0', '9a9cf33758671787e5006928188643fa',
+ 'a4a5a6a7a9aaabacaeafb0b1b3b4b5b6',
+ 'ecb-tbl-128: I=77'),
+ ('9091929364676619e6e7e0e1757a7b78', '2cddd634c846ba66bb46cbfea4a674f9',
+ 'b8b9babbbdbebfc0c2c3c4c5c7c8c9ca',
+ 'ecb-tbl-128: I=78'),
+ ('babbb8b98a89888f74757a7b92959497', 'd28bae029393c3e7e26e9fafbbb4b98f',
+ 'cccdcecfd1d2d3d4d6d7d8d9dbdcddde',
+ 'ecb-tbl-128: I=79'),
+ ('8d8c8f8e6e6d6c633b3a3d3ccad5d4d7', 'ec27529b1bee0a9ab6a0d73ebc82e9b7',
+ 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2',
+ 'ecb-tbl-128: I=80'),
+ ('86878485010203040808f7f767606162', '3cb25c09472aff6ee7e2b47ccd7ccb17',
+ 'f4f5f6f7f9fafbfcfefe010103040506',
+ 'ecb-tbl-128: I=81'),
+ ('8e8f8c8d656667788a8b8c8d010e0f0c', 'dee33103a7283370d725e44ca38f8fe5',
+ '08090a0b0d0e0f10121314151718191a',
+ 'ecb-tbl-128: I=82'),
+ ('c8c9cacb858687807a7b7475e7e0e1e2', '27f9bcd1aac64bffc11e7815702c1a69',
+ '1c1d1e1f21222324262728292b2c2d2e',
+ 'ecb-tbl-128: I=83'),
+ ('6d6c6f6e5053525d8c8d8a8badd2d3d0', '5df534ffad4ed0749a9988e9849d0021',
+ '30313233353637383a3b3c3d3f404142',
+ 'ecb-tbl-128: I=84'),
+ ('28292a2b393a3b3c0607181903040506', 'a48bee75db04fb60ca2b80f752a8421b',
+ '44454647494a4b4c4e4f505153545556',
+ 'ecb-tbl-128: I=85'),
+ ('a5a4a7a6b0b3b28ddbdadddcbdb2b3b0', '024c8cf70bc86ee5ce03678cb7af45f9',
+ '58595a5b5d5e5f60626364656768696a',
+ 'ecb-tbl-128: I=86'),
+ ('323330316467666130313e3f2c2b2a29', '3c19ac0f8a3a3862ce577831301e166b',
+ '6c6d6e6f71727374767778797b7c7d7e',
+ 'ecb-tbl-128: I=87'),
+ ('27262524080b0a05171611100b141516', 'c5e355b796a57421d59ca6be82e73bca',
+ '80818283858687888a8b8c8d8f909192',
+ 'ecb-tbl-128: I=88'),
+ ('040506074142434435340b0aa3a4a5a6', 'd94033276417abfb05a69d15b6e386e2',
+ '94959697999a9b9c9e9fa0a1a3a4a5a6',
+ 'ecb-tbl-128: I=89'),
+ ('242526271112130c61606766bdb2b3b0', '24b36559ea3a9b9b958fe6da3e5b8d85',
+ 'a8a9aaabadaeafb0b2b3b4b5b7b8b9ba',
+ 'ecb-tbl-128: I=90'),
+ ('4b4a4948252627209e9f9091cec9c8cb', '20fd4feaa0e8bf0cce7861d74ef4cb72',
+ 'bcbdbebfc1c2c3c4c6c7c8c9cbcccdce',
+ 'ecb-tbl-128: I=91'),
+ ('68696a6b6665646b9f9e9998d9e6e7e4', '350e20d5174277b9ec314c501570a11d',
+ 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2',
+ 'ecb-tbl-128: I=92'),
+ ('34353637c5c6c7c0f0f1eeef7c7b7a79', '87a29d61b7c604d238fe73045a7efd57',
+ 'e4e5e6e7e9eaebeceeeff0f1f3f4f5f6',
+ 'ecb-tbl-128: I=93'),
+ ('32333031c2c1c13f0d0c0b0a050a0b08', '2c3164c1cc7d0064816bdc0faa362c52',
+ 'f8f9fafbfdfefe00020304050708090a',
+ 'ecb-tbl-128: I=94'),
+ ('cdcccfcebebdbcbbabaaa5a4181f1e1d', '195fe5e8a05a2ed594f6e4400eee10b3',
+ '0c0d0e0f11121314161718191b1c1d1e',
+ 'ecb-tbl-128: I=95'),
+ ('212023223635343ba0a1a6a7445b5a59', 'e4663df19b9a21a5a284c2bd7f905025',
+ '20212223252627282a2b2c2d2f303132',
+ 'ecb-tbl-128: I=96'),
+ ('0e0f0c0da8abaaad2f2e515002050407', '21b88714cfb4e2a933bd281a2c4743fd',
+ '34353637393a3b3c3e3f404143444546',
+ 'ecb-tbl-128: I=97'),
+ ('070605042a2928378e8f8889bdb2b3b0', 'cbfc3980d704fd0fc54378ab84e17870',
+ '48494a4b4d4e4f50525354555758595a',
+ 'ecb-tbl-128: I=98'),
+ ('cbcac9c893909196a9a8a7a6a5a2a3a0', 'bc5144baa48bdeb8b63e22e03da418ef',
+ '5c5d5e5f61626364666768696b6c6d6e',
+ 'ecb-tbl-128: I=99'),
+ ('80818283c1c2c3cc9c9d9a9b0cf3f2f1', '5a1dbaef1ee2984b8395da3bdffa3ccc',
+ '70717273757677787a7b7c7d7f808182',
+ 'ecb-tbl-128: I=100'),
+ ('1213101125262720fafbe4e5b1b6b7b4', 'f0b11cd0729dfcc80cec903d97159574',
+ '84858687898a8b8c8e8f909193949596',
+ 'ecb-tbl-128: I=101'),
+ ('7f7e7d7c3033320d97969190222d2c2f', '9f95314acfddc6d1914b7f19a9cc8209',
+ '98999a9b9d9e9fa0a2a3a4a5a7a8a9aa',
+ 'ecb-tbl-128: I=102'),
+ ('4e4f4c4d484b4a4d81808f8e53545556', '595736f6f0f70914a94e9e007f022519',
+ 'acadaeafb1b2b3b4b6b7b8b9bbbcbdbe',
+ 'ecb-tbl-128: I=103'),
+ ('dcdddedfb0b3b2bd15141312a1bebfbc', '1f19f57892cae586fcdfb4c694deb183',
+ 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2',
+ 'ecb-tbl-128: I=104'),
+ ('93929190282b2a2dc4c5fafb92959497', '540700ee1f6f3dab0b3eddf6caee1ef5',
+ 'd4d5d6d7d9dadbdcdedfe0e1e3e4e5e6',
+ 'ecb-tbl-128: I=105'),
+ ('f5f4f7f6c4c7c6d9373631307e717073', '14a342a91019a331687a2254e6626ca2',
+ 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fa',
+ 'ecb-tbl-128: I=106'),
+ ('93929190b6b5b4b364656a6b05020300', '7b25f3c3b2eea18d743ef283140f29ff',
+ 'fcfdfeff01020304060708090b0c0d0e',
+ 'ecb-tbl-128: I=107'),
+ ('babbb8b90d0e0f00a4a5a2a3043b3a39', '46c2587d66e5e6fa7f7ca6411ad28047',
+ '10111213151617181a1b1c1d1f202122',
+ 'ecb-tbl-128: I=108'),
+ ('d8d9dadb7f7c7d7a10110e0f787f7e7d', '09470e72229d954ed5ee73886dfeeba9',
+ '24252627292a2b2c2e2f303133343536',
+ 'ecb-tbl-128: I=109'),
+ ('fefffcfdefeced923b3a3d3c6768696a', 'd77c03de92d4d0d79ef8d4824ef365eb',
+ '38393a3b3d3e3f40424344454748494a',
+ 'ecb-tbl-128: I=110'),
+ ('d6d7d4d58a89888f96979899a5a2a3a0', '1d190219f290e0f1715d152d41a23593',
+ '4c4d4e4f51525354565758595b5c5d5e',
+ 'ecb-tbl-128: I=111'),
+ ('18191a1ba8abaaa5303136379b848586', 'a2cd332ce3a0818769616292e87f757b',
+ '60616263656667686a6b6c6d6f707172',
+ 'ecb-tbl-128: I=112'),
+ ('6b6a6968a4a7a6a1d6d72829b0b7b6b5', 'd54afa6ce60fbf9341a3690e21385102',
+ '74757677797a7b7c7e7f808183848586',
+ 'ecb-tbl-128: I=113'),
+ ('000102038a89889755545352a6a9a8ab', '06e5c364ded628a3f5e05e613e356f46',
+ '88898a8b8d8e8f90929394959798999a',
+ 'ecb-tbl-128: I=114'),
+ ('2d2c2f2eb3b0b1b6b6b7b8b9f2f5f4f7', 'eae63c0e62556dac85d221099896355a',
+ '9c9d9e9fa1a2a3a4a6a7a8a9abacadae',
+ 'ecb-tbl-128: I=115'),
+ ('979695943536373856575051e09f9e9d', '1fed060e2c6fc93ee764403a889985a2',
+ 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2',
+ 'ecb-tbl-128: I=116'),
+ ('a4a5a6a7989b9a9db1b0afae7a7d7c7f', 'c25235c1a30fdec1c7cb5c5737b2a588',
+ 'c4c5c6c7c9cacbcccecfd0d1d3d4d5d6',
+ 'ecb-tbl-128: I=117'),
+ ('c1c0c3c2686b6a55a8a9aeafeae5e4e7', '796dbef95147d4d30873ad8b7b92efc0',
+ 'd8d9dadbdddedfe0e2e3e4e5e7e8e9ea',
+ 'ecb-tbl-128: I=118'),
+ ('c1c0c3c2141716118c8d828364636261', 'cbcf0fb34d98d0bd5c22ce37211a46bf',
+ 'ecedeeeff1f2f3f4f6f7f8f9fbfcfdfe',
+ 'ecb-tbl-128: I=119'),
+ ('93929190cccfcec196979091e0fffefd', '94b44da6466126cafa7c7fd09063fc24',
+ '00010203050607080a0b0c0d0f101112',
+ 'ecb-tbl-128: I=120'),
+ ('b4b5b6b7f9fafbfc25241b1a6e69686b', 'd78c5b5ebf9b4dbda6ae506c5074c8fe',
+ '14151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-128: I=121'),
+ ('868784850704051ac7c6c1c08788898a', '6c27444c27204b043812cf8cf95f9769',
+ '28292a2b2d2e2f30323334353738393a',
+ 'ecb-tbl-128: I=122'),
+ ('f4f5f6f7aaa9a8affdfcf3f277707172', 'be94524ee5a2aa50bba8b75f4c0aebcf',
+ '3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-128: I=123'),
+ ('d3d2d1d00605040bc3c2c5c43e010003', 'a0aeaae91ba9f31f51aeb3588cf3a39e',
+ '50515253555657585a5b5c5d5f606162',
+ 'ecb-tbl-128: I=124'),
+ ('73727170424140476a6b74750d0a0b08', '275297779c28266ef9fe4c6a13c08488',
+ '64656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-128: I=125'),
+ ('c2c3c0c10a0908f754555253a1aeafac', '86523d92bb8672cb01cf4a77fd725882',
+ '78797a7b7d7e7f80828384858788898a',
+ 'ecb-tbl-128: I=126'),
+ ('6d6c6f6ef8fbfafd82838c8df8fffefd', '4b8327640e9f33322a04dd96fcbf9a36',
+ '8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-128: I=127'),
+ ('f5f4f7f684878689a6a7a0a1d2cdcccf', 'ce52af650d088ca559425223f4d32694',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2',
+ 'ecb-tbl-128: I=128'),
+
+ # ecb_tbl.txt, KEYSIZE=192
+ ('2d33eef2c0430a8a9ebf45e809c40bb6', 'dff4945e0336df4c1c56bc700eff837f',
+ '00010203050607080a0b0c0d0f10111214151617191a1b1c',
+ 'ecb-tbl-192: I=1'),
+ ('6aa375d1fa155a61fb72353e0a5a8756', 'b6fddef4752765e347d5d2dc196d1252',
+ '1e1f20212324252628292a2b2d2e2f30323334353738393a',
+ 'ecb-tbl-192: I=2'),
+ ('bc3736518b9490dcb8ed60eb26758ed4', 'd23684e3d963b3afcf1a114aca90cbd6',
+ '3c3d3e3f41424344464748494b4c4d4e5051525355565758',
+ 'ecb-tbl-192: I=3'),
+ ('aa214402b46cffb9f761ec11263a311e', '3a7ac027753e2a18c2ceab9e17c11fd0',
+ '5a5b5c5d5f60616264656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-192: I=4'),
+ ('02aea86e572eeab66b2c3af5e9a46fd6', '8f6786bd007528ba26603c1601cdd0d8',
+ '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394',
+ 'ecb-tbl-192: I=5'),
+ ('e2aef6acc33b965c4fa1f91c75ff6f36', 'd17d073b01e71502e28b47ab551168b3',
+ '969798999b9c9d9ea0a1a2a3a5a6a7a8aaabacadafb0b1b2',
+ 'ecb-tbl-192: I=6'),
+ ('0659df46427162b9434865dd9499f91d', 'a469da517119fab95876f41d06d40ffa',
+ 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6c8c9cacbcdcecfd0',
+ 'ecb-tbl-192: I=7'),
+ ('49a44239c748feb456f59c276a5658df', '6091aa3b695c11f5c0b6ad26d3d862ff',
+ 'd2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-192: I=8'),
+ ('66208f6e9d04525bdedb2733b6a6be37', '70f9e67f9f8df1294131662dc6e69364',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c',
+ 'ecb-tbl-192: I=9'),
+ ('3393f8dfc729c97f5480b950bc9666b0', 'd154dcafad8b207fa5cbc95e9996b559',
+ '0e0f10111314151618191a1b1d1e1f20222324252728292a',
+ 'ecb-tbl-192: I=10'),
+ ('606834c8ce063f3234cf1145325dbd71', '4934d541e8b46fa339c805a7aeb9e5da',
+ '2c2d2e2f31323334363738393b3c3d3e4041424345464748',
+ 'ecb-tbl-192: I=11'),
+ ('fec1c04f529bbd17d8cecfcc4718b17f', '62564c738f3efe186e1a127a0c4d3c61',
+ '4a4b4c4d4f50515254555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-192: I=12'),
+ ('32df99b431ed5dc5acf8caf6dc6ce475', '07805aa043986eb23693e23bef8f3438',
+ '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384',
+ 'ecb-tbl-192: I=13'),
+ ('7fdc2b746f3f665296943b83710d1f82', 'df0b4931038bade848dee3b4b85aa44b',
+ '868788898b8c8d8e90919293959697989a9b9c9d9fa0a1a2',
+ 'ecb-tbl-192: I=14'),
+ ('8fba1510a3c5b87e2eaa3f7a91455ca2', '592d5fded76582e4143c65099309477c',
+ 'a4a5a6a7a9aaabacaeafb0b1b3b4b5b6b8b9babbbdbebfc0',
+ 'ecb-tbl-192: I=15'),
+ ('2c9b468b1c2eed92578d41b0716b223b', 'c9b8d6545580d3dfbcdd09b954ed4e92',
+ 'c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde',
+ 'ecb-tbl-192: I=16'),
+ ('0a2bbf0efc6bc0034f8a03433fca1b1a', '5dccd5d6eb7c1b42acb008201df707a0',
+ 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfc',
+ 'ecb-tbl-192: I=17'),
+ ('25260e1f31f4104d387222e70632504b', 'a2a91682ffeb6ed1d34340946829e6f9',
+ 'fefe01010304050608090a0b0d0e0f10121314151718191a',
+ 'ecb-tbl-192: I=18'),
+ ('c527d25a49f08a5228d338642ae65137', 'e45d185b797000348d9267960a68435d',
+ '1c1d1e1f21222324262728292b2c2d2e3031323335363738',
+ 'ecb-tbl-192: I=19'),
+ ('3b49fc081432f5890d0e3d87e884a69e', '45e060dae5901cda8089e10d4f4c246b',
+ '3a3b3c3d3f40414244454647494a4b4c4e4f505153545556',
+ 'ecb-tbl-192: I=20'),
+ ('d173f9ed1e57597e166931df2754a083', 'f6951afacc0079a369c71fdcff45df50',
+ '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374',
+ 'ecb-tbl-192: I=21'),
+ ('8c2b7cafa5afe7f13562daeae1adede0', '9e95e00f351d5b3ac3d0e22e626ddad6',
+ '767778797b7c7d7e80818283858687888a8b8c8d8f909192',
+ 'ecb-tbl-192: I=22'),
+ ('aaf4ec8c1a815aeb826cab741339532c', '9cb566ff26d92dad083b51fdc18c173c',
+ '94959697999a9b9c9e9fa0a1a3a4a5a6a8a9aaabadaeafb0',
+ 'ecb-tbl-192: I=23'),
+ ('40be8c5d9108e663f38f1a2395279ecf', 'c9c82766176a9b228eb9a974a010b4fb',
+ 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebec',
+ 'ecb-tbl-192: I=24'),
+ ('0c8ad9bc32d43e04716753aa4cfbe351', 'd8e26aa02945881d5137f1c1e1386e88',
+ '2a2b2c2d2f30313234353637393a3b3c3e3f404143444546',
+ 'ecb-tbl-192: I=25'),
+ ('1407b1d5f87d63357c8dc7ebbaebbfee', 'c0e024ccd68ff5ffa4d139c355a77c55',
+ '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364',
+ 'ecb-tbl-192: I=26'),
+ ('e62734d1ae3378c4549e939e6f123416', '0b18b3d16f491619da338640df391d43',
+ '84858687898a8b8c8e8f90919394959698999a9b9d9e9fa0',
+ 'ecb-tbl-192: I=27'),
+ ('5a752cff2a176db1a1de77f2d2cdee41', 'dbe09ac8f66027bf20cb6e434f252efc',
+ 'a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe',
+ 'ecb-tbl-192: I=28'),
+ ('a9c8c3a4eabedc80c64730ddd018cd88', '6d04e5e43c5b9cbe05feb9606b6480fe',
+ 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdc',
+ 'ecb-tbl-192: I=29'),
+ ('ee9b3dbbdb86180072130834d305999a', 'dd1d6553b96be526d9fee0fbd7176866',
+ '1a1b1c1d1f20212224252627292a2b2c2e2f303133343536',
+ 'ecb-tbl-192: I=30'),
+ ('a7fa8c3586b8ebde7568ead6f634a879', '0260ca7e3f979fd015b0dd4690e16d2a',
+ '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354',
+ 'ecb-tbl-192: I=31'),
+ ('37e0f4a87f127d45ac936fe7ad88c10a', '9893734de10edcc8a67c3b110b8b8cc6',
+ '929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae',
+ 'ecb-tbl-192: I=32'),
+ ('3f77d8b5d92bac148e4e46f697a535c5', '93b30b750516b2d18808d710c2ee84ef',
+ '464748494b4c4d4e50515253555657585a5b5c5d5f606162',
+ 'ecb-tbl-192: I=33'),
+ ('d25ebb686c40f7e2c4da1014936571ca', '16f65fa47be3cb5e6dfe7c6c37016c0e',
+ '828384858788898a8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-192: I=34'),
+ ('4f1c769d1e5b0552c7eca84dea26a549', 'f3847210d5391e2360608e5acb560581',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbc',
+ 'ecb-tbl-192: I=35'),
+ ('8548e2f882d7584d0fafc54372b6633a', '8754462cd223366d0753913e6af2643d',
+ 'bebfc0c1c3c4c5c6c8c9cacbcdcecfd0d2d3d4d5d7d8d9da',
+ 'ecb-tbl-192: I=36'),
+ ('87d7a336cb476f177cd2a51af2a62cdf', '1ea20617468d1b806a1fd58145462017',
+ 'dcdddedfe1e2e3e4e6e7e8e9ebecedeef0f1f2f3f5f6f7f8',
+ 'ecb-tbl-192: I=37'),
+ ('03b1feac668c4e485c1065dfc22b44ee', '3b155d927355d737c6be9dda60136e2e',
+ 'fafbfcfdfe01000204050607090a0b0c0e0f101113141516',
+ 'ecb-tbl-192: I=38'),
+ ('bda15e66819fa72d653a6866aa287962', '26144f7b66daa91b6333dbd3850502b3',
+ '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334',
+ 'ecb-tbl-192: I=39'),
+ ('4d0c7a0d2505b80bf8b62ceb12467f0a', 'e4f9a4ab52ced8134c649bf319ebcc90',
+ '363738393b3c3d3e40414243454647484a4b4c4d4f505152',
+ 'ecb-tbl-192: I=40'),
+ ('626d34c9429b37211330986466b94e5f', 'b9ddd29ac6128a6cab121e34a4c62b36',
+ '54555657595a5b5c5e5f60616364656668696a6b6d6e6f70',
+ 'ecb-tbl-192: I=41'),
+ ('333c3e6bf00656b088a17e5ff0e7f60a', '6fcddad898f2ce4eff51294f5eaaf5c9',
+ '727374757778797a7c7d7e7f81828384868788898b8c8d8e',
+ 'ecb-tbl-192: I=42'),
+ ('687ed0cdc0d2a2bc8c466d05ef9d2891', 'c9a6fe2bf4028080bea6f7fc417bd7e3',
+ '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabac',
+ 'ecb-tbl-192: I=43'),
+ ('487830e78cc56c1693e64b2a6660c7b6', '6a2026846d8609d60f298a9c0673127f',
+ 'aeafb0b1b3b4b5b6b8b9babbbdbebfc0c2c3c4c5c7c8c9ca',
+ 'ecb-tbl-192: I=44'),
+ ('7a48d6b7b52b29392aa2072a32b66160', '2cb25c005e26efea44336c4c97a4240b',
+ 'cccdcecfd1d2d3d4d6d7d8d9dbdcdddee0e1e2e3e5e6e7e8',
+ 'ecb-tbl-192: I=45'),
+ ('907320e64c8c5314d10f8d7a11c8618d', '496967ab8680ddd73d09a0e4c7dcc8aa',
+ 'eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506',
+ 'ecb-tbl-192: I=46'),
+ ('b561f2ca2d6e65a4a98341f3ed9ff533', 'd5af94de93487d1f3a8c577cb84a66a4',
+ '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324',
+ 'ecb-tbl-192: I=47'),
+ ('df769380d212792d026f049e2e3e48ef', '84bdac569cae2828705f267cc8376e90',
+ '262728292b2c2d2e30313233353637383a3b3c3d3f404142',
+ 'ecb-tbl-192: I=48'),
+ ('79f374bc445bdabf8fccb8843d6054c6', 'f7401dda5ad5ab712b7eb5d10c6f99b6',
+ '44454647494a4b4c4e4f50515354555658595a5b5d5e5f60',
+ 'ecb-tbl-192: I=49'),
+ ('4e02f1242fa56b05c68dbae8fe44c9d6', '1c9d54318539ebd4c3b5b7e37bf119f0',
+ '626364656768696a6c6d6e6f71727374767778797b7c7d7e',
+ 'ecb-tbl-192: I=50'),
+ ('cf73c93cbff57ac635a6f4ad2a4a1545', 'aca572d65fb2764cffd4a6eca090ea0d',
+ '80818283858687888a8b8c8d8f90919294959697999a9b9c',
+ 'ecb-tbl-192: I=51'),
+ ('9923548e2875750725b886566784c625', '36d9c627b8c2a886a10ccb36eae3dfbb',
+ '9e9fa0a1a3a4a5a6a8a9aaabadaeafb0b2b3b4b5b7b8b9ba',
+ 'ecb-tbl-192: I=52'),
+ ('4888336b723a022c9545320f836a4207', '010edbf5981e143a81d646e597a4a568',
+ 'bcbdbebfc1c2c3c4c6c7c8c9cbcccdced0d1d2d3d5d6d7d8',
+ 'ecb-tbl-192: I=53'),
+ ('f84d9a5561b0608b1160dee000c41ba8', '8db44d538dc20cc2f40f3067fd298e60',
+ 'dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6',
+ 'ecb-tbl-192: I=54'),
+ ('c23192a0418e30a19b45ae3e3625bf22', '930eb53bc71e6ac4b82972bdcd5aafb3',
+ 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314',
+ 'ecb-tbl-192: I=55'),
+ ('b84e0690b28b0025381ad82a15e501a7', '6c42a81edcbc9517ccd89c30c95597b4',
+ '161718191b1c1d1e20212223252627282a2b2c2d2f303132',
+ 'ecb-tbl-192: I=56'),
+ ('acef5e5c108876c4f06269f865b8f0b0', 'da389847ad06df19d76ee119c71e1dd3',
+ '34353637393a3b3c3e3f40414344454648494a4b4d4e4f50',
+ 'ecb-tbl-192: I=57'),
+ ('0f1b3603e0f5ddea4548246153a5e064', 'e018fdae13d3118f9a5d1a647a3f0462',
+ '525354555758595a5c5d5e5f61626364666768696b6c6d6e',
+ 'ecb-tbl-192: I=58'),
+ ('fbb63893450d42b58c6d88cd3c1809e3', '2aa65db36264239d3846180fabdfad20',
+ '70717273757677787a7b7c7d7f80818284858687898a8b8c',
+ 'ecb-tbl-192: I=59'),
+ ('4bef736df150259dae0c91354e8a5f92', '1472163e9a4f780f1ceb44b07ecf4fdb',
+ '8e8f90919394959698999a9b9d9e9fa0a2a3a4a5a7a8a9aa',
+ 'ecb-tbl-192: I=60'),
+ ('7d2d46242056ef13d3c3fc93c128f4c7', 'c8273fdc8f3a9f72e91097614b62397c',
+ 'acadaeafb1b2b3b4b6b7b8b9bbbcbdbec0c1c2c3c5c6c7c8',
+ 'ecb-tbl-192: I=61'),
+ ('e9c1ba2df415657a256edb33934680fd', '66c8427dcd733aaf7b3470cb7d976e3f',
+ 'cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6',
+ 'ecb-tbl-192: I=62'),
+ ('e23ee277b0aa0a1dfb81f7527c3514f1', '146131cb17f1424d4f8da91e6f80c1d0',
+ 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304',
+ 'ecb-tbl-192: I=63'),
+ ('3e7445b0b63caaf75e4a911e12106b4c', '2610d0ad83659081ae085266a88770dc',
+ '060708090b0c0d0e10111213151617181a1b1c1d1f202122',
+ 'ecb-tbl-192: I=64'),
+ ('767774752023222544455a5be6e1e0e3', '38a2b5a974b0575c5d733917fb0d4570',
+ '24252627292a2b2c2e2f30313334353638393a3b3d3e3f40',
+ 'ecb-tbl-192: I=65'),
+ ('72737475717e7f7ce9e8ebea696a6b6c', 'e21d401ebc60de20d6c486e4f39a588b',
+ '424344454748494a4c4d4e4f51525354565758595b5c5d5e',
+ 'ecb-tbl-192: I=66'),
+ ('dfdedddc25262728c9c8cfcef1eeefec', 'e51d5f88c670b079c0ca1f0c2c4405a2',
+ '60616263656667686a6b6c6d6f70717274757677797a7b7c',
+ 'ecb-tbl-192: I=67'),
+ ('fffe0100707776755f5e5d5c7675746b', '246a94788a642fb3d1b823c8762380c8',
+ '7e7f80818384858688898a8b8d8e8f90929394959798999a',
+ 'ecb-tbl-192: I=68'),
+ ('e0e1e2e3424140479f9e9190292e2f2c', 'b80c391c5c41a4c3b30c68e0e3d7550f',
+ '9c9d9e9fa1a2a3a4a6a7a8a9abacadaeb0b1b2b3b5b6b7b8',
+ 'ecb-tbl-192: I=69'),
+ ('2120272690efeeed3b3a39384e4d4c4b', 'b77c4754fc64eb9a1154a9af0bb1f21c',
+ 'babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6',
+ 'ecb-tbl-192: I=70'),
+ ('ecedeeef5350516ea1a0a7a6a3acadae', 'fb554de520d159a06bf219fc7f34a02f',
+ 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4',
+ 'ecb-tbl-192: I=71'),
+ ('32333c3d25222320e9e8ebeacecdccc3', 'a89fba152d76b4927beed160ddb76c57',
+ 'f6f7f8f9fbfcfdfe00010203050607080a0b0c0d0f101112',
+ 'ecb-tbl-192: I=72'),
+ ('40414243626160678a8bb4b511161714', '5676eab4a98d2e8473b3f3d46424247c',
+ '14151617191a1b1c1e1f20212324252628292a2b2d2e2f30',
+ 'ecb-tbl-192: I=73'),
+ ('94959293f5fafbf81f1e1d1c7c7f7e79', '4e8f068bd7ede52a639036ec86c33568',
+ '323334353738393a3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-192: I=74'),
+ ('bebfbcbd191a1b14cfcec9c8546b6a69', 'f0193c4d7aff1791ee4c07eb4a1824fc',
+ '50515253555657585a5b5c5d5f60616264656667696a6b6c',
+ 'ecb-tbl-192: I=75'),
+ ('2c2d3233898e8f8cbbbab9b8333031ce', 'ac8686eeca9ba761afe82d67b928c33f',
+ '6e6f70717374757678797a7b7d7e7f80828384858788898a',
+ 'ecb-tbl-192: I=76'),
+ ('84858687bfbcbdba37363938fdfafbf8', '5faf8573e33b145b6a369cd3606ab2c9',
+ '8c8d8e8f91929394969798999b9c9d9ea0a1a2a3a5a6a7a8',
+ 'ecb-tbl-192: I=77'),
+ ('828384857669686b909192930b08090e', '31587e9944ab1c16b844ecad0df2e7da',
+ 'aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6',
+ 'ecb-tbl-192: I=78'),
+ ('bebfbcbd9695948b707176779e919093', 'd017fecd91148aba37f6f3068aa67d8a',
+ 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4',
+ 'ecb-tbl-192: I=79'),
+ ('8b8a85846067666521202322d0d3d2dd', '788ef2f021a73cba2794b616078a8500',
+ 'e6e7e8e9ebecedeef0f1f2f3f5f6f7f8fafbfcfdfe010002',
+ 'ecb-tbl-192: I=80'),
+ ('76777475f1f2f3f4f8f9e6e777707172', '5d1ef20dced6bcbc12131ac7c54788aa',
+ '04050607090a0b0c0e0f10111314151618191a1b1d1e1f20',
+ 'ecb-tbl-192: I=81'),
+ ('a4a5a2a34f404142b4b5b6b727242522', 'b3c8cf961faf9ea05fdde6d1e4d8f663',
+ '222324252728292a2c2d2e2f31323334363738393b3c3d3e',
+ 'ecb-tbl-192: I=82'),
+ ('94959697e1e2e3ec16171011839c9d9e', '143075c70605861c7fac6526199e459f',
+ '40414243454647484a4b4c4d4f50515254555657595a5b5c',
+ 'ecb-tbl-192: I=83'),
+ ('03023d3c06010003dedfdcddfffcfde2', 'a5ae12eade9a87268d898bfc8fc0252a',
+ '5e5f60616364656668696a6b6d6e6f70727374757778797a',
+ 'ecb-tbl-192: I=84'),
+ ('10111213f1f2f3f4cecfc0c1dbdcddde', '0924f7cf2e877a4819f5244a360dcea9',
+ '7c7d7e7f81828384868788898b8c8d8e9091929395969798',
+ 'ecb-tbl-192: I=85'),
+ ('67666160724d4c4f1d1c1f1e73707176', '3d9e9635afcc3e291cc7ab3f27d1c99a',
+ '9a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6',
+ 'ecb-tbl-192: I=86'),
+ ('e6e7e4e5a8abaad584858283909f9e9d', '9d80feebf87510e2b8fb98bb54fd788c',
+ 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4',
+ 'ecb-tbl-192: I=87'),
+ ('71707f7e565150537d7c7f7e6162636c', '5f9d1a082a1a37985f174002eca01309',
+ 'd6d7d8d9dbdcdddee0e1e2e3e5e6e7e8eaebecedeff0f1f2',
+ 'ecb-tbl-192: I=88'),
+ ('64656667212223245555aaaa03040506', 'a390ebb1d1403930184a44b4876646e4',
+ 'f4f5f6f7f9fafbfcfefe01010304050608090a0b0d0e0f10',
+ 'ecb-tbl-192: I=89'),
+ ('9e9f9899aba4a5a6cfcecdcc2b28292e', '700fe918981c3195bb6c4bcb46b74e29',
+ '121314151718191a1c1d1e1f21222324262728292b2c2d2e',
+ 'ecb-tbl-192: I=90'),
+ ('c7c6c5c4d1d2d3dc626364653a454447', '907984406f7bf2d17fb1eb15b673d747',
+ '30313233353637383a3b3c3d3f40414244454647494a4b4c',
+ 'ecb-tbl-192: I=91'),
+ ('f6f7e8e9e0e7e6e51d1c1f1e5b585966', 'c32a956dcfc875c2ac7c7cc8b8cc26e1',
+ '4e4f50515354555658595a5b5d5e5f60626364656768696a',
+ 'ecb-tbl-192: I=92'),
+ ('bcbdbebf5d5e5f5868696667f4f3f2f1', '02646e2ebfa9b820cf8424e9b9b6eb51',
+ '6c6d6e6f71727374767778797b7c7d7e8081828385868788',
+ 'ecb-tbl-192: I=93'),
+ ('40414647b0afaead9b9a99989b98999e', '621fda3a5bbd54c6d3c685816bd4ead8',
+ '8a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6',
+ 'ecb-tbl-192: I=94'),
+ ('69686b6a0201001f0f0e0908b4bbbab9', 'd4e216040426dfaf18b152469bc5ac2f',
+ 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4',
+ 'ecb-tbl-192: I=95'),
+ ('c7c6c9c8d8dfdedd5a5b5859bebdbcb3', '9d0635b9d33b6cdbd71f5d246ea17cc8',
+ 'c6c7c8c9cbcccdced0d1d2d3d5d6d7d8dadbdcdddfe0e1e2',
+ 'ecb-tbl-192: I=96'),
+ ('dedfdcdd787b7a7dfffee1e0b2b5b4b7', '10abad1bd9bae5448808765583a2cc1a',
+ 'e4e5e6e7e9eaebeceeeff0f1f3f4f5f6f8f9fafbfdfefe00',
+ 'ecb-tbl-192: I=97'),
+ ('4d4c4b4a606f6e6dd0d1d2d3fbf8f9fe', '6891889e16544e355ff65a793c39c9a8',
+ '020304050708090a0c0d0e0f11121314161718191b1c1d1e',
+ 'ecb-tbl-192: I=98'),
+ ('b7b6b5b4d7d4d5dae5e4e3e2e1fefffc', 'cc735582e68072c163cd9ddf46b91279',
+ '20212223252627282a2b2c2d2f30313234353637393a3b3c',
+ 'ecb-tbl-192: I=99'),
+ ('cecfb0b1f7f0f1f2aeafacad3e3d3c23', 'c5c68b9aeeb7f878df578efa562f9574',
+ '3e3f40414344454648494a4b4d4e4f50525354555758595a',
+ 'ecb-tbl-192: I=100'),
+ ('cacbc8c9cdcecfc812131c1d494e4f4c', '5f4764395a667a47d73452955d0d2ce8',
+ '5c5d5e5f61626364666768696b6c6d6e7071727375767778',
+ 'ecb-tbl-192: I=101'),
+ ('9d9c9b9ad22d2c2fb1b0b3b20c0f0e09', '701448331f66106cefddf1eb8267c357',
+ '7a7b7c7d7f80818284858687898a8b8c8e8f909193949596',
+ 'ecb-tbl-192: I=102'),
+ ('7a7b787964676659959493924f404142', 'cb3ee56d2e14b4e1941666f13379d657',
+ '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4',
+ 'ecb-tbl-192: I=103'),
+ ('aaaba4a5cec9c8cb1f1e1d1caba8a9a6', '9fe16efd18ab6e1981191851fedb0764',
+ 'b6b7b8b9bbbcbdbec0c1c2c3c5c6c7c8cacbcccdcfd0d1d2',
+ 'ecb-tbl-192: I=104'),
+ ('93929190282b2a2dc4c5fafb92959497', '3dc9ba24e1b223589b147adceb4c8e48',
+ 'd4d5d6d7d9dadbdcdedfe0e1e3e4e5e6e8e9eaebedeeeff0',
+ 'ecb-tbl-192: I=105'),
+ ('efeee9e8ded1d0d339383b3a888b8a8d', '1c333032682e7d4de5e5afc05c3e483c',
+ 'f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e',
+ 'ecb-tbl-192: I=106'),
+ ('7f7e7d7ca2a1a0af78797e7f112e2f2c', 'd593cc99a95afef7e92038e05a59d00a',
+ '10111213151617181a1b1c1d1f20212224252627292a2b2c',
+ 'ecb-tbl-192: I=107'),
+ ('84859a9b2b2c2d2e868784852625245b', '51e7f96f53b4353923452c222134e1ec',
+ '2e2f30313334353638393a3b3d3e3f40424344454748494a',
+ 'ecb-tbl-192: I=108'),
+ ('b0b1b2b3070405026869666710171615', '4075b357a1a2b473400c3b25f32f81a4',
+ '4c4d4e4f51525354565758595b5c5d5e6061626365666768',
+ 'ecb-tbl-192: I=109'),
+ ('acadaaabbda2a3a00d0c0f0e595a5b5c', '302e341a3ebcd74f0d55f61714570284',
+ '6a6b6c6d6f70717274757677797a7b7c7e7f808183848586',
+ 'ecb-tbl-192: I=110'),
+ ('121310115655544b5253545569666764', '57abdd8231280da01c5042b78cf76522',
+ '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4',
+ 'ecb-tbl-192: I=111'),
+ ('dedfd0d166616063eaebe8e94142434c', '17f9ea7eea17ac1adf0e190fef799e92',
+ 'a6a7a8a9abacadaeb0b1b2b3b5b6b7b8babbbcbdbfc0c1c2',
+ 'ecb-tbl-192: I=112'),
+ ('dbdad9d81417161166677879e0e7e6e5', '2e1bdd563dd87ee5c338dd6d098d0a7a',
+ 'c4c5c6c7c9cacbcccecfd0d1d3d4d5d6d8d9dadbdddedfe0',
+ 'ecb-tbl-192: I=113'),
+ ('6a6b6c6de0efeeed2b2a2928c0c3c2c5', 'eb869996e6f8bfb2bfdd9e0c4504dbb2',
+ 'e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe',
+ 'ecb-tbl-192: I=114'),
+ ('b1b0b3b21714151a1a1b1c1d5649484b', 'c2e01549e9decf317468b3e018c61ba8',
+ '00010203050607080a0b0c0d0f10111214151617191a1b1c',
+ 'ecb-tbl-192: I=115'),
+ ('39380706a3a4a5a6c4c5c6c77271706f', '8da875d033c01dd463b244a1770f4a22',
+ '1e1f20212324252628292a2b2d2e2f30323334353738393a',
+ 'ecb-tbl-192: I=116'),
+ ('5c5d5e5f1013121539383736e2e5e4e7', '8ba0dcf3a186844f026d022f8839d696',
+ '3c3d3e3f41424344464748494b4c4d4e5051525355565758',
+ 'ecb-tbl-192: I=117'),
+ ('43424544ead5d4d72e2f2c2d64676661', 'e9691ff9a6cc6970e51670a0fd5b88c1',
+ '5a5b5c5d5f60616264656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-192: I=118'),
+ ('55545756989b9a65f8f9feff18171615', 'f2baec06faeed30f88ee63ba081a6e5b',
+ '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394',
+ 'ecb-tbl-192: I=119'),
+ ('05040b0a525554573c3d3e3f4a494847', '9c39d4c459ae5753394d6094adc21e78',
+ '969798999b9c9d9ea0a1a2a3a5a6a7a8aaabacadafb0b1b2',
+ 'ecb-tbl-192: I=120'),
+ ('14151617595a5b5c8584fbfa8e89888b', '6345b532a11904502ea43ba99c6bd2b2',
+ 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6c8c9cacbcdcecfd0',
+ 'ecb-tbl-192: I=121'),
+ ('7c7d7a7bfdf2f3f029282b2a51525354', '5ffae3061a95172e4070cedce1e428c8',
+ 'd2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-192: I=122'),
+ ('38393a3b1e1d1c1341404746c23d3c3e', '0a4566be4cdf9adce5dec865b5ab34cd',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c',
+ 'ecb-tbl-192: I=123'),
+ ('8d8c939240474645818083827c7f7e41', 'ca17fcce79b7404f2559b22928f126fb',
+ '0e0f10111314151618191a1b1d1e1f20222324252728292a',
+ 'ecb-tbl-192: I=124'),
+ ('3b3a39381a19181f32333c3d45424340', '97ca39b849ed73a6470a97c821d82f58',
+ '2c2d2e2f31323334363738393b3c3d3e4041424345464748',
+ 'ecb-tbl-192: I=125'),
+ ('f0f1f6f738272625828380817f7c7d7a', '8198cb06bc684c6d3e9b7989428dcf7a',
+ '4a4b4c4d4f50515254555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-192: I=126'),
+ ('89888b8a0407061966676061141b1a19', 'f53c464c705ee0f28d9a4c59374928bd',
+ '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384',
+ 'ecb-tbl-192: I=127'),
+ ('d3d2dddcaaadacaf9c9d9e9fe8ebeae5', '9adb3d4cca559bb98c3e2ed73dbf1154',
+ '868788898b8c8d8e90919293959697989a9b9c9d9fa0a1a2',
+ 'ecb-tbl-192: I=128'),
+
+ # ecb_tbl.txt, KEYSIZE=256
+ ('834eadfccac7e1b30664b1aba44815ab', '1946dabf6a03a2a2c3d0b05080aed6fc',
+ '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-256: I=1'),
+ ('d9dc4dba3021b05d67c0518f72b62bf1', '5ed301d747d3cc715445ebdec62f2fb4',
+ '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-256: I=2'),
+ ('a291d86301a4a739f7392173aa3c604c', '6585c8f43d13a6beab6419fc5935b9d0',
+ '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-256: I=3'),
+ ('4264b2696498de4df79788a9f83e9390', '2a5b56a596680fcc0e05f5e0f151ecae',
+ '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-256: I=4'),
+ ('ee9932b3721804d5a83ef5949245b6f6', 'f5d6ff414fd2c6181494d20c37f2b8c4',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6',
+ 'ecb-tbl-256: I=5'),
+ ('e6248f55c5fdcbca9cbbb01c88a2ea77', '85399c01f59fffb5204f19f8482f00b8',
+ 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-256: I=6'),
+ ('b8358e41b9dff65fd461d55a99266247', '92097b4c88a041ddf98144bc8d22e8e7',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516',
+ 'ecb-tbl-256: I=7'),
+ ('f0e2d72260af58e21e015ab3a4c0d906', '89bd5b73b356ab412aef9f76cea2d65c',
+ '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e',
+ 'ecb-tbl-256: I=8'),
+ ('475b8b823ce8893db3c44a9f2a379ff7', '2536969093c55ff9454692f2fac2f530',
+ '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-256: I=9'),
+ ('688f5281945812862f5f3076cf80412f', '07fc76a872843f3f6e0081ee9396d637',
+ '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e',
+ 'ecb-tbl-256: I=10'),
+ ('08d1d2bc750af553365d35e75afaceaa', 'e38ba8ec2aa741358dcc93e8f141c491',
+ '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6',
+ 'ecb-tbl-256: I=11'),
+ ('8707121f47cc3efceca5f9a8474950a1', 'd028ee23e4a89075d0b03e868d7d3a42',
+ 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde',
+ 'ecb-tbl-256: I=12'),
+ ('e51aa0b135dba566939c3b6359a980c5', '8cd9423dfc459e547155c5d1d522e540',
+ 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506',
+ 'ecb-tbl-256: I=13'),
+ ('069a007fc76a459f98baf917fedf9521', '080e9517eb1677719acf728086040ae3',
+ '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e',
+ 'ecb-tbl-256: I=14'),
+ ('726165c1723fbcf6c026d7d00b091027', '7c1700211a3991fc0ecded0ab3e576b0',
+ '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556',
+ 'ecb-tbl-256: I=15'),
+ ('d7c544de91d55cfcde1f84ca382200ce', 'dabcbcc855839251db51e224fbe87435',
+ '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e',
+ 'ecb-tbl-256: I=16'),
+ ('fed3c9a161b9b5b2bd611b41dc9da357', '68d56fad0406947a4dd27a7448c10f1d',
+ '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6',
+ 'ecb-tbl-256: I=17'),
+ ('4f634cdc6551043409f30b635832cf82', 'da9a11479844d1ffee24bbf3719a9925',
+ 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce',
+ 'ecb-tbl-256: I=18'),
+ ('109ce98db0dfb36734d9f3394711b4e6', '5e4ba572f8d23e738da9b05ba24b8d81',
+ 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6',
+ 'ecb-tbl-256: I=19'),
+ ('4ea6dfaba2d8a02ffdffa89835987242', 'a115a2065d667e3f0b883837a6e903f8',
+ '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596',
+ 'ecb-tbl-256: I=20'),
+ ('5ae094f54af58e6e3cdbf976dac6d9ef', '3e9e90dc33eac2437d86ad30b137e66e',
+ '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe',
+ 'ecb-tbl-256: I=21'),
+ ('764d8e8e0f29926dbe5122e66354fdbe', '01ce82d8fbcdae824cb3c48e495c3692',
+ 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6',
+ 'ecb-tbl-256: I=22'),
+ ('3f0418f888cdf29a982bf6b75410d6a9', '0c9cff163ce936faaf083cfd3dea3117',
+ 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e',
+ 'ecb-tbl-256: I=23'),
+ ('e4a3e7cb12cdd56aa4a75197a9530220', '5131ba9bd48f2bba85560680df504b52',
+ '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536',
+ 'ecb-tbl-256: I=24'),
+ ('211677684aac1ec1a160f44c4ebf3f26', '9dc503bbf09823aec8a977a5ad26ccb2',
+ '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e',
+ 'ecb-tbl-256: I=25'),
+ ('d21e439ff749ac8f18d6d4b105e03895', '9a6db0c0862e506a9e397225884041d7',
+ '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586',
+ 'ecb-tbl-256: I=26'),
+ ('d9f6ff44646c4725bd4c0103ff5552a7', '430bf9570804185e1ab6365fc6a6860c',
+ '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae',
+ 'ecb-tbl-256: I=27'),
+ ('0b1256c2a00b976250cfc5b0c37ed382', '3525ebc02f4886e6a5a3762813e8ce8a',
+ 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6',
+ 'ecb-tbl-256: I=28'),
+ ('b056447ffc6dc4523a36cc2e972a3a79', '07fa265c763779cce224c7bad671027b',
+ 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe',
+ 'ecb-tbl-256: I=29'),
+ ('5e25ca78f0de55802524d38da3fe4456', 'e8b72b4e8be243438c9fff1f0e205872',
+ '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-256: I=30'),
+ ('a5bcf4728fa5eaad8567c0dc24675f83', '109d4f999a0e11ace1f05e6b22cbcb50',
+ '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-256: I=31'),
+ ('814e59f97ed84646b78b2ca022e9ca43', '45a5e8d4c3ed58403ff08d68a0cc4029',
+ '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-256: I=32'),
+ ('15478beec58f4775c7a7f5d4395514d7', '196865964db3d417b6bd4d586bcb7634',
+ '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-256: I=33'),
+ ('253548ffca461c67c8cbc78cd59f4756', '60436ad45ac7d30d99195f815d98d2ae',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6',
+ 'ecb-tbl-256: I=34'),
+ ('fd7ad8d73b9b0f8cc41600640f503d65', 'bb07a23f0b61014b197620c185e2cd75',
+ 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-256: I=35'),
+ ('06199de52c6cbf8af954cd65830bcd56', '5bc0b2850129c854423aff0751fe343b',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516',
+ 'ecb-tbl-256: I=36'),
+ ('f17c4ffe48e44c61bd891e257e725794', '7541a78f96738e6417d2a24bd2beca40',
+ '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e',
+ 'ecb-tbl-256: I=37'),
+ ('9a5b4a402a3e8a59be6bf5cd8154f029', 'b0a303054412882e464591f1546c5b9e',
+ '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-256: I=38'),
+ ('79bd40b91a7e07dc939d441782ae6b17', '778c06d8a355eeee214fcea14b4e0eef',
+ '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e',
+ 'ecb-tbl-256: I=39'),
+ ('d8ceaaf8976e5fbe1012d8c84f323799', '09614206d15cbace63227d06db6beebb',
+ '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6',
+ 'ecb-tbl-256: I=40'),
+ ('3316e2751e2e388b083da23dd6ac3fbe', '41b97fb20e427a9fdbbb358d9262255d',
+ 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde',
+ 'ecb-tbl-256: I=41'),
+ ('8b7cfbe37de7dca793521819242c5816', 'c1940f703d845f957652c2d64abd7adf',
+ 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506',
+ 'ecb-tbl-256: I=42'),
+ ('f23f033c0eebf8ec55752662fd58ce68', 'd2d44fcdae5332343366db297efcf21b',
+ '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e',
+ 'ecb-tbl-256: I=43'),
+ ('59eb34f6c8bdbacc5fc6ad73a59a1301', 'ea8196b79dbe167b6aa9896e287eed2b',
+ '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556',
+ 'ecb-tbl-256: I=44'),
+ ('dcde8b6bd5cf7cc22d9505e3ce81261a', 'd6b0b0c4ba6c7dbe5ed467a1e3f06c2d',
+ '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e',
+ 'ecb-tbl-256: I=45'),
+ ('e33cf7e524fed781e7042ff9f4b35dc7', 'ec51eb295250c22c2fb01816fb72bcae',
+ '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6',
+ 'ecb-tbl-256: I=46'),
+ ('27963c8facdf73062867d164df6d064c', 'aded6630a07ce9c7408a155d3bd0d36f',
+ 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce',
+ 'ecb-tbl-256: I=47'),
+ ('77b1ce386b551b995f2f2a1da994eef8', '697c9245b9937f32f5d1c82319f0363a',
+ 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6',
+ 'ecb-tbl-256: I=48'),
+ ('f083388b013679efcf0bb9b15d52ae5c', 'aad5ad50c6262aaec30541a1b7b5b19c',
+ 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314161718191b1c1d1e',
+ 'ecb-tbl-256: I=49'),
+ ('c5009e0dab55db0abdb636f2600290c8', '7d34b893855341ec625bd6875ac18c0d',
+ '20212223252627282a2b2c2d2f30313234353637393a3b3c3e3f404143444546',
+ 'ecb-tbl-256: I=50'),
+ ('7804881e26cd532d8514d3683f00f1b9', '7ef05105440f83862f5d780e88f02b41',
+ '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364666768696b6c6d6e',
+ 'ecb-tbl-256: I=51'),
+ ('46cddcd73d1eb53e675ca012870a92a3', 'c377c06403382061af2c9c93a8e70df6',
+ '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596',
+ 'ecb-tbl-256: I=52'),
+ ('a9fb44062bb07fe130a8e8299eacb1ab', '1dbdb3ffdc052dacc83318853abc6de5',
+ '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe',
+ 'ecb-tbl-256: I=53'),
+ ('2b6ff8d7a5cc3a28a22d5a6f221af26b', '69a6eab00432517d0bf483c91c0963c7',
+ 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6',
+ 'ecb-tbl-256: I=54'),
+ ('1a9527c29b8add4b0e3e656dbb2af8b4', '0797f41dc217c80446e1d514bd6ab197',
+ 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e',
+ 'ecb-tbl-256: I=55'),
+ ('7f99cf2c75244df015eb4b0c1050aeae', '9dfd76575902a637c01343c58e011a03',
+ '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536',
+ 'ecb-tbl-256: I=56'),
+ ('e84ff85b0d9454071909c1381646c4ed', 'acf4328ae78f34b9fa9b459747cc2658',
+ '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e',
+ 'ecb-tbl-256: I=57'),
+ ('89afd40f99521280d5399b12404f6db4', 'b0479aea12bac4fe2384cf98995150c6',
+ '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586',
+ 'ecb-tbl-256: I=58'),
+ ('a09ef32dbc5119a35ab7fa38656f0329', '9dd52789efe3ffb99f33b3da5030109a',
+ '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae',
+ 'ecb-tbl-256: I=59'),
+ ('61773457f068c376c7829b93e696e716', 'abbb755e4621ef8f1214c19f649fb9fd',
+ 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6',
+ 'ecb-tbl-256: I=60'),
+ ('a34f0cae726cce41dd498747d891b967', 'da27fb8174357bce2bed0e7354f380f9',
+ 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe',
+ 'ecb-tbl-256: I=61'),
+ ('856f59496c7388ee2d2b1a27b7697847', 'c59a0663f0993838f6e5856593bdc5ef',
+ '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-256: I=62'),
+ ('cb090c593ef7720bd95908fb93b49df4', 'ed60b264b5213e831607a99c0ce5e57e',
+ '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-256: I=63'),
+ ('a0ac75cd2f1923d460fc4d457ad95baf', 'e50548746846f3eb77b8c520640884ed',
+ '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-256: I=64'),
+ ('2a2b282974777689e8e9eeef525d5c5f', '28282cc7d21d6a2923641e52d188ef0c',
+ '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-256: I=65'),
+ ('909192939390919e0f0e09089788898a', '0dfa5b02abb18e5a815305216d6d4f8e',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6',
+ 'ecb-tbl-256: I=66'),
+ ('777675748d8e8f907170777649464744', '7359635c0eecefe31d673395fb46fb99',
+ 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-256: I=67'),
+ ('717073720605040b2d2c2b2a05fafbf9', '73c679f7d5aef2745c9737bb4c47fb36',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516',
+ 'ecb-tbl-256: I=68'),
+ ('64656667fefdfcc31b1a1d1ca5aaaba8', 'b192bd472a4d2eafb786e97458967626',
+ '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e',
+ 'ecb-tbl-256: I=69'),
+ ('dbdad9d86a696867b5b4b3b2c8d7d6d5', '0ec327f6c8a2b147598ca3fde61dc6a4',
+ '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-256: I=70'),
+ ('5c5d5e5fe3e0e1fe31303736333c3d3e', 'fc418eb3c41b859b38d4b6f646629729',
+ '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e',
+ 'ecb-tbl-256: I=71'),
+ ('545556574b48494673727574546b6a69', '30249e5ac282b1c981ea64b609f3a154',
+ '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6',
+ 'ecb-tbl-256: I=72'),
+ ('ecedeeefc6c5c4bb56575051f5fafbf8', '5e6e08646d12150776bb43c2d78a9703',
+ 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde',
+ 'ecb-tbl-256: I=73'),
+ ('464744452724252ac9c8cfced2cdcccf', 'faeb3d5de652cd3447dceb343f30394a',
+ 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506',
+ 'ecb-tbl-256: I=74'),
+ ('e6e7e4e54142435c878681801c131211', 'a8e88706823f6993ef80d05c1c7b2cf0',
+ '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e',
+ 'ecb-tbl-256: I=75'),
+ ('72737071cfcccdc2f9f8fffe710e0f0c', '8ced86677e6e00a1a1b15968f2d3cce6',
+ '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556',
+ 'ecb-tbl-256: I=76'),
+ ('505152537370714ec3c2c5c4010e0f0c', '9fc7c23858be03bdebb84e90db6786a9',
+ '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e',
+ 'ecb-tbl-256: I=77'),
+ ('a8a9aaab5c5f5e51aeafa8a93d222320', 'b4fbd65b33f70d8cf7f1111ac4649c36',
+ '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6',
+ 'ecb-tbl-256: I=78'),
+ ('dedfdcddf6f5f4eb10111617fef1f0f3', 'c5c32d5ed03c4b53cc8c1bd0ef0dbbf6',
+ 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce',
+ 'ecb-tbl-256: I=79'),
+ ('bdbcbfbe5e5d5c530b0a0d0cfac5c4c7', 'd1a7f03b773e5c212464b63709c6a891',
+ 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6',
+ 'ecb-tbl-256: I=80'),
+ ('8a8b8889050606f8f4f5f2f3636c6d6e', '6b7161d8745947ac6950438ea138d028',
+ 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314161718191b1c1d1e',
+ 'ecb-tbl-256: I=81'),
+ ('a6a7a4a54d4e4f40b2b3b4b539262724', 'fd47a9f7e366ee7a09bc508b00460661',
+ '20212223252627282a2b2c2d2f30313234353637393a3b3c3e3f404143444546',
+ 'ecb-tbl-256: I=82'),
+ ('9c9d9e9fe9eaebf40e0f08099b949596', '00d40b003dc3a0d9310b659b98c7e416',
+ '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364666768696b6c6d6e',
+ 'ecb-tbl-256: I=83'),
+ ('2d2c2f2e1013121dcccdcacbed121310', 'eea4c79dcc8e2bda691f20ac48be0717',
+ '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596',
+ 'ecb-tbl-256: I=84'),
+ ('f4f5f6f7edeeefd0eaebecedf7f8f9fa', 'e78f43b11c204403e5751f89d05a2509',
+ '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe',
+ 'ecb-tbl-256: I=85'),
+ ('3d3c3f3e282b2a2573727574150a0b08', 'd0f0e3d1f1244bb979931e38dd1786ef',
+ 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6',
+ 'ecb-tbl-256: I=86'),
+ ('b6b7b4b5f8fbfae5b4b5b2b3a0afaead', '042e639dc4e1e4dde7b75b749ea6f765',
+ 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e',
+ 'ecb-tbl-256: I=87'),
+ ('b7b6b5b4989b9a95878681809ba4a5a6', 'bc032fdd0efe29503a980a7d07ab46a8',
+ '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536',
+ 'ecb-tbl-256: I=88'),
+ ('a8a9aaabe5e6e798e9e8efee4748494a', '0c93ac949c0da6446effb86183b6c910',
+ '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e',
+ 'ecb-tbl-256: I=89'),
+ ('ecedeeefd9dadbd4b9b8bfbe657a7b78', 'e0d343e14da75c917b4a5cec4810d7c2',
+ '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586',
+ 'ecb-tbl-256: I=90'),
+ ('7f7e7d7c696a6b74cacbcccd929d9c9f', '0eafb821748408279b937b626792e619',
+ '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae',
+ 'ecb-tbl-256: I=91'),
+ ('08090a0b0605040bfffef9f8b9c6c7c4', 'fa1ac6e02d23b106a1fef18b274a553f',
+ 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6',
+ 'ecb-tbl-256: I=92'),
+ ('08090a0bf1f2f3ccfcfdfafb68676665', '0dadfe019cd12368075507df33c1a1e9',
+ 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe',
+ 'ecb-tbl-256: I=93'),
+ ('cacbc8c93a393837050403020d121310', '3a0879b414465d9ffbaf86b33a63a1b9',
+ '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-256: I=94'),
+ ('e9e8ebea8281809f8f8e8988343b3a39', '62199fadc76d0be1805d3ba0b7d914bf',
+ '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-256: I=95'),
+ ('515053524645444bd0d1d6d7340b0a09', '1b06d6c5d333e742730130cf78e719b4',
+ '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-256: I=96'),
+ ('42434041ecefee1193929594c6c9c8cb', 'f1f848824c32e9dcdcbf21580f069329',
+ '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e',
+ 'ecb-tbl-256: I=97'),
+ ('efeeedecc2c1c0cf76777071455a5b58', '1a09050cbd684f784d8e965e0782f28a',
+ 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6',
+ 'ecb-tbl-256: I=98'),
+ ('5f5e5d5c3f3c3d221d1c1b1a19161714', '79c2969e7ded2ba7d088f3f320692360',
+ 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee',
+ 'ecb-tbl-256: I=99'),
+ ('000102034142434c1c1d1a1b8d727371', '091a658a2f7444c16accb669450c7b63',
+ 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516',
+ 'ecb-tbl-256: I=100'),
+ ('8e8f8c8db1b2b38c56575051050a0b08', '97c1e3a72cca65fa977d5ed0e8a7bbfc',
+ '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e',
+ 'ecb-tbl-256: I=101'),
+ ('a7a6a5a4e8ebeae57f7e7978cad5d4d7', '70c430c6db9a17828937305a2df91a2a',
+ '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566',
+ 'ecb-tbl-256: I=102'),
+ ('8a8b888994979689454443429f909192', '629553457fbe2479098571c7c903fde8',
+ '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e',
+ 'ecb-tbl-256: I=103'),
+ ('8c8d8e8fe0e3e2ed45444342f1cecfcc', 'a25b25a61f612669e7d91265c7d476ba',
+ '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6',
+ 'ecb-tbl-256: I=104'),
+ ('fffefdfc4c4f4e31d8d9dedfb6b9b8bb', 'eb7e4e49b8ae0f024570dda293254fed',
+ 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde',
+ 'ecb-tbl-256: I=105'),
+ ('fdfcfffecccfcec12f2e29286679787b', '38fe15d61cca84516e924adce5014f67',
+ 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506',
+ 'ecb-tbl-256: I=106'),
+ ('67666564bab9b8a77071767719161714', '3ad208492249108c9f3ebeb167ad0583',
+ '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e',
+ 'ecb-tbl-256: I=107'),
+ ('9a9b98992d2e2f2084858283245b5a59', '299ba9f9bf5ab05c3580fc26edd1ed12',
+ '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556',
+ 'ecb-tbl-256: I=108'),
+ ('a4a5a6a70b0809365c5d5a5b2c232221', '19dc705b857a60fb07717b2ea5717781',
+ '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e',
+ 'ecb-tbl-256: I=109'),
+ ('464744455754555af3f2f5f4afb0b1b2', 'ffc8aeb885b5efcad06b6dbebf92e76b',
+ '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6',
+ 'ecb-tbl-256: I=110'),
+ ('323330317675746b7273747549464744', 'f58900c5e0b385253ff2546250a0142b',
+ 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce',
+ 'ecb-tbl-256: I=111'),
+ ('a8a9aaab181b1a15808186872b141516', '2ee67b56280bc462429cee6e3370cbc1',
+ 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6',
+ 'ecb-tbl-256: I=112'),
+ ('e7e6e5e4202323ddaaabacad343b3a39', '20db650a9c8e9a84ab4d25f7edc8f03f',
+ 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314161718191b1c1d1e',
+ 'ecb-tbl-256: I=113'),
+ ('a8a9aaab2221202fedecebea1e010003', '3c36da169525cf818843805f25b78ae5',
+ '20212223252627282a2b2c2d2f30313234353637393a3b3c3e3f404143444546',
+ 'ecb-tbl-256: I=114'),
+ ('f9f8fbfa5f5c5d42424344450e010003', '9a781d960db9e45e37779042fea51922',
+ '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364666768696b6c6d6e',
+ 'ecb-tbl-256: I=115'),
+ ('57565554f5f6f7f89697909120dfdedd', '6560395ec269c672a3c288226efdba77',
+ '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596',
+ 'ecb-tbl-256: I=116'),
+ ('f8f9fafbcccfcef1dddcdbda0e010003', '8c772b7a189ac544453d5916ebb27b9a',
+ '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe',
+ 'ecb-tbl-256: I=117'),
+ ('d9d8dbda7073727d80818687c2dddcdf', '77ca5468cc48e843d05f78eed9d6578f',
+ 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6',
+ 'ecb-tbl-256: I=118'),
+ ('c5c4c7c6080b0a1588898e8f68676665', '72cdcc71dc82c60d4429c9e2d8195baa',
+ 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e',
+ 'ecb-tbl-256: I=119'),
+ ('83828180dcdfded186878081f0cfcecd', '8080d68ce60e94b40b5b8b69eeb35afa',
+ '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536',
+ 'ecb-tbl-256: I=120'),
+ ('98999a9bdddedfa079787f7e0a050407', '44222d3cde299c04369d58ac0eba1e8e',
+ '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e',
+ 'ecb-tbl-256: I=121'),
+ ('cecfcccd4f4c4d429f9e9998dfc0c1c2', '9b8721b0a8dfc691c5bc5885dbfcb27a',
+ '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586',
+ 'ecb-tbl-256: I=122'),
+ ('404142436665647b29282f2eaba4a5a6', '0dc015ce9a3a3414b5e62ec643384183',
+ '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae',
+ 'ecb-tbl-256: I=123'),
+ ('33323130e6e5e4eb23222524dea1a0a3', '705715448a8da412025ce38345c2a148',
+ 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6',
+ 'ecb-tbl-256: I=124'),
+ ('cfcecdccf6f5f4cbe6e7e0e199969794', 'c32b5b0b6fbae165266c569f4b6ecf0b',
+ 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe',
+ 'ecb-tbl-256: I=125'),
+ ('babbb8b97271707fdcdddadb29363734', '4dca6c75192a01ddca9476af2a521e87',
+ '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526',
+ 'ecb-tbl-256: I=126'),
+ ('c9c8cbca4447465926272021545b5a59', '058691e627ecbc36ac07b6db423bd698',
+ '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e',
+ 'ecb-tbl-256: I=127'),
+ ('050407067477767956575051221d1c1f', '7444527095838fe080fc2bcdd30847eb',
+ '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576',
+ 'ecb-tbl-256: I=128'),
+
+ # FIPS PUB 800-38A test vectors, 2001 edition. Annex F.
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '3ad77bb40d7a3660a89ecaf32466ef97'+'f5d3d58503b9699de785895a96fdbaaf'+
+ '43b1cd7f598ece23881b00e3ed030688'+'7b0c785e27e8ad3f8223207104725dd4',
+ '2b7e151628aed2a6abf7158809cf4f3c',
+ 'NIST 800-38A, F.1.1, ECB and AES-128'),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ 'bd334f1d6e45f25ff712a214571fa5cc'+'974104846d0ad3ad7734ecb3ecee4eef'+
+ 'ef7afd2270e2e60adce0ba2face6444e'+'9a4b41ba738d6c72fb16691603c18e0e',
+ '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+ 'NIST 800-38A, F.1.3, ECB and AES-192'),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ 'f3eed1bdb5d2a03c064b5a7e3db181f8'+'591ccb10d410ed26dc5ba74a31362870'+
+ 'b6ed21b99ca6f4f9f153e7b1beafed1d'+'23304b7a39f9f3ff067d8d8f9e24ecc7',
+ '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+ 'NIST 800-38A, F.1.3, ECB and AES-256'),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '7649abac8119b246cee98e9b12e9197d'+'5086cb9b507219ee95db113a917678b2'+
+ '73bed6b8e3c1743b7116e69e22229516'+'3ff1caa1681fac09120eca307586e1a7',
+ '2b7e151628aed2a6abf7158809cf4f3c',
+ 'NIST 800-38A, F.2.1, CBC and AES-128',
+ dict(mode='CBC', iv='000102030405060708090a0b0c0d0e0f')),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '4f021db243bc633d7178183a9fa071e8'+'b4d9ada9ad7dedf4e5e738763f69145a'+
+ '571b242012fb7ae07fa9baac3df102e0'+'08b0e27988598881d920a9e64f5615cd',
+ '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+ 'NIST 800-38A, F.2.1, CBC and AES-192',
+ dict(mode='CBC', iv='000102030405060708090a0b0c0d0e0f')),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ 'f58c4c04d6e5f1ba779eabfb5f7bfbd6'+'9cfc4e967edb808d679f777bc6702c7d'+
+ '39f23369a9d9bacfa530e26304231461'+'b2eb05e2c39be9fcda6c19078c6a9d1b',
+ '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+ 'NIST 800-38A, F.2.1, CBC and AES-256',
+ dict(mode='CBC', iv='000102030405060708090a0b0c0d0e0f')),
+
+ # Skip CFB-1 since it is not supported by PyCrypto
+
+ ('6bc1bee22e409f96e93d7e117393172aae2d','3b79424c9c0dd436bace9e0ed4586a4f32b9',
+ '2b7e151628aed2a6abf7158809cf4f3c',
+ 'NIST 800-38A, F.3.7, CFB-8 and AES-128',
+ dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=8)),
+
+ ('6bc1bee22e409f96e93d7e117393172aae2d','cda2521ef0a905ca44cd057cbf0d47a0678a',
+ '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+ 'NIST 800-38A, F.3.9, CFB-8 and AES-192',
+ dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=8)),
+
+ ('6bc1bee22e409f96e93d7e117393172aae2d','dc1f1a8520a64db55fcc8ac554844e889700',
+ '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+ 'NIST 800-38A, F.3.11, CFB-8 and AES-256',
+ dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=8)),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '3b3fd92eb72dad20333449f8e83cfb4a'+'c8a64537a0b3a93fcde3cdad9f1ce58b'+
+ '26751f67a3cbb140b1808cf187a4f4df'+'c04b05357c5d1c0eeac4c66f9ff7f2e6',
+ '2b7e151628aed2a6abf7158809cf4f3c',
+ 'NIST 800-38A, F.3.13, CFB-128 and AES-128',
+ dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=128)),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ 'cdc80d6fddf18cab34c25909c99a4174'+'67ce7f7f81173621961a2b70171d3d7a'+
+ '2e1e8a1dd59b88b1c8e60fed1efac4c9'+'c05f9f9ca9834fa042ae8fba584b09ff',
+ '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+ 'NIST 800-38A, F.3.15, CFB-128 and AES-192',
+ dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=128)),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ 'dc7e84bfda79164b7ecd8486985d3860'+'39ffed143b28b1c832113c6331e5407b'+
+ 'df10132415e54b92a13ed0a8267ae2f9'+'75a385741ab9cef82031623d55b1e471',
+ '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+ 'NIST 800-38A, F.3.17, CFB-128 and AES-256',
+ dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=128)),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '3b3fd92eb72dad20333449f8e83cfb4a'+'7789508d16918f03f53c52dac54ed825'+
+ '9740051e9c5fecf64344f7a82260edcc'+'304c6528f659c77866a510d9c1d6ae5e',
+ '2b7e151628aed2a6abf7158809cf4f3c',
+ 'NIST 800-38A, F.4.1, OFB and AES-128',
+ dict(mode='OFB', iv='000102030405060708090a0b0c0d0e0f')),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ 'cdc80d6fddf18cab34c25909c99a4174'+'fcc28b8d4c63837c09e81700c1100401'+
+ '8d9a9aeac0f6596f559c6d4daf59a5f2'+'6d9f200857ca6c3e9cac524bd9acc92a',
+ '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+ 'NIST 800-38A, F.4.3, OFB and AES-192',
+ dict(mode='OFB', iv='000102030405060708090a0b0c0d0e0f')),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ 'dc7e84bfda79164b7ecd8486985d3860'+'4febdc6740d20b3ac88f6ad82a4fb08d'+
+ '71ab47a086e86eedf39d1c5bba97c408'+'0126141d67f37be8538f5a8be740e484',
+ '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+ 'NIST 800-38A, F.4.5, OFB and AES-256',
+ dict(mode='OFB', iv='000102030405060708090a0b0c0d0e0f')),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '874d6191b620e3261bef6864990db6ce'+'9806f66b7970fdff8617187bb9fffdff'+
+ '5ae4df3edbd5d35e5b4f09020db03eab'+'1e031dda2fbe03d1792170a0f3009cee',
+ '2b7e151628aed2a6abf7158809cf4f3c',
+ 'NIST 800-38A, F.5.1, CTR and AES-128',
+ dict(mode='CTR', ctr_params=dict(nbits=16, prefix='f0f1f2f3f4f5f6f7f8f9fafbfcfd', initial_value=0xfeff))),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '1abc932417521ca24f2b0459fe7e6e0b'+'090339ec0aa6faefd5ccc2c6f4ce8e94'+
+ '1e36b26bd1ebc670d1bd1d665620abf7'+'4f78a7f6d29809585a97daec58c6b050',
+ '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+ 'NIST 800-38A, F.5.3, CTR and AES-192',
+ dict(mode='CTR', ctr_params=dict(nbits=16, prefix='f0f1f2f3f4f5f6f7f8f9fafbfcfd', initial_value=0xfeff))),
+
+ ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+
+ '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710',
+ '601ec313775789a5b7a7f504bbf3d228'+'f443e3ca4d62b59aca84e990cacaf5c5'+
+ '2b0930daa23de94ce87017ba2d84988d'+'dfc9c58db67aada613c2dd08457941a6',
+ '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+ 'NIST 800-38A, F.5.5, CTR and AES-256',
+ dict(mode='CTR', ctr_params=dict(nbits=16, prefix='f0f1f2f3f4f5f6f7f8f9fafbfcfd', initial_value=0xfeff))),
+
+ # RFC 3686 test vectors
+ # This is a list of (plaintext, ciphertext, key[, description[, params]]) tuples.
+ ('53696e676c6520626c6f636b206d7367', 'e4095d4fb7a7b3792d6175a3261311b8',
+ 'ae6852f8121067cc4bf7a5765577f39e',
+ 'RFC 3686 Test Vector #1: Encrypting 16 octets using AES-CTR with 128-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00000030'+'0000000000000000'))),
+ ('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+ '5104a106168a72d9790d41ee8edad388eb2e1efc46da57c8fce630df9141be28',
+ '7e24067817fae0d743d6ce1f32539163',
+ 'RFC 3686 Test Vector #2: Encrypting 32 octets using AES-CTR with 128-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='006cb6db'+'c0543b59da48d90b'))),
+ ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f'+'20212223',
+ 'c1cf48a89f2ffdd9cf4652e9efdb72d7'+'4540a42bde6d7836d59a5ceaaef31053'+'25b2072f',
+ '7691be035e5020a8ac6e618529f9a0dc',
+ 'RFC 3686 Test Vector #3: Encrypting 36 octets using AES-CTR with 128-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00e0017b'+'27777f3f4a1786f0'))),
+ ('53696e676c6520626c6f636b206d7367',
+ '4b55384fe259c9c84e7935a003cbe928',
+ '16af5b145fc9f579c175f93e3bfb0eed'+'863d06ccfdb78515',
+ 'RFC 3686 Test Vector #4: Encrypting 16 octets using AES-CTR with 192-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00000048'+'36733c147d6d93cb'))),
+ ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f',
+ '453243fc609b23327edfaafa7131cd9f'+'8490701c5ad4a79cfc1fe0ff42f4fb00',
+ '7c5cb2401b3dc33c19e7340819e0f69c'+'678c3db8e6f6a91a',
+ 'RFC 3686 Test Vector #5: Encrypting 32 octets using AES-CTR with 192-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='0096b03b'+'020c6eadc2cb500d'))),
+ ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f'+'20212223',
+ '96893fc55e5c722f540b7dd1ddf7e758'+'d288bc95c69165884536c811662f2188'+'abee0935',
+ '02bf391ee8ecb159b959617b0965279b'+'f59b60a786d3e0fe',
+ 'RFC 3686 Test Vector #6: Encrypting 36 octets using AES-CTR with 192-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='0007bdfd'+'5cbd60278dcc0912'))),
+ ('53696e676c6520626c6f636b206d7367',
+ '145ad01dbf824ec7560863dc71e3e0c0',
+ '776beff2851db06f4c8a0542c8696f6c'+'6a81af1eec96b4d37fc1d689e6c1c104',
+ 'RFC 3686 Test Vector #7: Encrypting 16 octets using AES-CTR with 256-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00000060'+'db5672c97aa8f0b2'))),
+ ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f',
+ 'f05e231b3894612c49ee000b804eb2a9'+'b8306b508f839d6a5530831d9344af1c',
+ 'f6d66d6bd52d59bb0796365879eff886'+'c66dd51a5b6a99744b50590c87a23884',
+ 'RFC 3686 Test Vector #8: Encrypting 32 octets using AES-CTR with 256-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00faac24'+'c1585ef15a43d875'))),
+ ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f'+'20212223',
+ 'eb6c52821d0bbbf7ce7594462aca4faa'+'b407df866569fd07f48cc0b583d6071f'+'1ec0e6b8',
+ 'ff7a617ce69148e4f1726e2f43581de2'+'aa62d9f805532edff1eed687fb54153d',
+ 'RFC 3686 Test Vector #9: Encrypting 36 octets using AES-CTR with 256-bit key',
+ dict(mode='CTR', ctr_params=dict(nbits=32, prefix='001cc5b7'+'51a51d70a1c11148'))),
+
+ # The following test vectors have been generated with gpg v1.4.0.
+ # The command line used was:
+ #
+ # gpg -c -z 0 --cipher-algo AES --passphrase secret_passphrase \
+ # --disable-mdc --s2k-mode 0 --output ct pt
+ #
+ # As result, the content of the file 'pt' is encrypted with a key derived
+ # from 'secret_passphrase' and written to file 'ct'.
+ # Test vectors must be extracted from 'ct', which is a collection of
+ # TLVs (see RFC4880 for all details):
+ # - the encrypted data (with the encrypted IV as prefix) is the payload
+ # of the TLV with tag 9 (Symmetrical Encrypted Data Packet).
+ # This is the ciphertext in the test vector.
+ # - inside the encrypted part, there is a further layer of TLVs. One must
+ # look for tag 11 (Literal Data Packet); in its payload, after a short
+ # but time dependent header, there is the content of file 'pt'.
+ # In the test vector, the plaintext is the complete set of TLVs that gets
+ # encrypted. It is not just the content of 'pt'.
+ # - the key is the leftmost 16 bytes of the SHA1 digest of the password.
+ # The test vector contains such shortened digest.
+ #
+ # Note that encryption uses a clear IV, and decryption an encrypted IV
+ ( 'ac18620270744fb4f647426c61636b4361745768697465436174', # Plaintext, 'BlackCatWhiteCat'
+ 'dc6b9e1f095de609765c59983db5956ae4f63aea7405389d2ebb', # Ciphertext
+ '5baa61e4c9b93f3f0682250b6cf8331b', # Key (hash of 'password')
+ 'GPG Test Vector #1',
+ dict(mode='OPENPGP', iv='3d7d3e62282add7eb203eeba5c800733', encrypted_iv='fd934601ef49cb58b6d9aebca6056bdb96ef' ) ),
+]
+
+def get_tests(config={}):
+ from Crypto.Cipher import AES
+ from common import make_block_tests
+ return make_block_tests(AES, "AES", test_data)
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC2.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC2.py
new file mode 100644
index 000000000..b6bc519cb
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC2.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/ARC2.py: Self-test for the Alleged-RC2 cipher
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.ARC2"""
+
+__revision__ = "$Id$"
+
+from common import dict # For compatibility with Python 2.1 and 2.2
+
+import unittest
+from Crypto.Util.py3compat import *
+
+# This is a list of (plaintext, ciphertext, key[, description[, extra_params]]) tuples.
+test_data = [
+ # Test vectors from RFC 2268
+
+ # 63-bit effective key length
+ ('0000000000000000', 'ebb773f993278eff', '0000000000000000',
+ 'RFC2268-1', dict(effective_keylen=63)),
+
+ # 64-bit effective key length
+ ('ffffffffffffffff', '278b27e42e2f0d49', 'ffffffffffffffff',
+ 'RFC2268-2', dict(effective_keylen=64)),
+ ('1000000000000001', '30649edf9be7d2c2', '3000000000000000',
+ 'RFC2268-3', dict(effective_keylen=64)),
+ ('0000000000000000', '61a8a244adacccf0', '88',
+ 'RFC2268-4', dict(effective_keylen=64)),
+ ('0000000000000000', '6ccf4308974c267f', '88bca90e90875a',
+ 'RFC2268-5', dict(effective_keylen=64)),
+ ('0000000000000000', '1a807d272bbe5db1', '88bca90e90875a7f0f79c384627bafb2',
+ 'RFC2268-6', dict(effective_keylen=64)),
+
+ # 128-bit effective key length
+ ('0000000000000000', '2269552ab0f85ca6', '88bca90e90875a7f0f79c384627bafb2',
+ "RFC2268-7", dict(effective_keylen=128)),
+ ('0000000000000000', '5b78d3a43dfff1f1',
+ '88bca90e90875a7f0f79c384627bafb216f80a6f85920584c42fceb0be255daf1e',
+ "RFC2268-8", dict(effective_keylen=129)),
+
+ # Test vectors from PyCrypto 2.0.1's testdata.py
+ # 1024-bit effective key length
+ ('0000000000000000', '624fb3e887419e48', '5068696c6970476c617373',
+ 'PCTv201-0'),
+ ('ffffffffffffffff', '79cadef44c4a5a85', '5068696c6970476c617373',
+ 'PCTv201-1'),
+ ('0001020304050607', '90411525b34e4c2c', '5068696c6970476c617373',
+ 'PCTv201-2'),
+ ('0011223344556677', '078656aaba61cbfb', '5068696c6970476c617373',
+ 'PCTv201-3'),
+ ('0000000000000000', 'd7bcc5dbb4d6e56a', 'ffffffffffffffff',
+ 'PCTv201-4'),
+ ('ffffffffffffffff', '7259018ec557b357', 'ffffffffffffffff',
+ 'PCTv201-5'),
+ ('0001020304050607', '93d20a497f2ccb62', 'ffffffffffffffff',
+ 'PCTv201-6'),
+ ('0011223344556677', 'cb15a7f819c0014d', 'ffffffffffffffff',
+ 'PCTv201-7'),
+ ('0000000000000000', '63ac98cdf3843a7a', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553',
+ 'PCTv201-8'),
+ ('ffffffffffffffff', '3fb49e2fa12371dd', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553',
+ 'PCTv201-9'),
+ ('0001020304050607', '46414781ab387d5f', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553',
+ 'PCTv201-10'),
+ ('0011223344556677', 'be09dc81feaca271', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553',
+ 'PCTv201-11'),
+ ('0000000000000000', 'e64221e608be30ab', '53e5ffe553',
+ 'PCTv201-12'),
+ ('ffffffffffffffff', '862bc60fdcd4d9a9', '53e5ffe553',
+ 'PCTv201-13'),
+ ('0001020304050607', '6a34da50fa5e47de', '53e5ffe553',
+ 'PCTv201-14'),
+ ('0011223344556677', '584644c34503122c', '53e5ffe553',
+ 'PCTv201-15'),
+]
+
+class BufferOverflowTest(unittest.TestCase):
+ # Test a buffer overflow found in older versions of PyCrypto
+
+ def setUp(self):
+ global ARC2
+ from Crypto.Cipher import ARC2
+
+ def runTest(self):
+ """ARC2 with keylength > 128"""
+ key = "x" * 16384
+ mode = ARC2.MODE_ECB
+ self.assertRaises(ValueError, ARC2.new, key, mode)
+
+def get_tests(config={}):
+ from Crypto.Cipher import ARC2
+ from common import make_block_tests
+
+ tests = make_block_tests(ARC2, "ARC2", test_data)
+ tests.append(BufferOverflowTest())
+
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC4.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC4.py
new file mode 100644
index 000000000..4e039d1c0
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_ARC4.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/ARC4.py: Self-test for the Alleged-RC4 cipher
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.ARC4"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (plaintext, ciphertext, key[, description]) tuples.
+test_data = [
+ # Test vectors from Eric Rescorla's message with the subject
+ # "RC4 compatibility testing", sent to the cipherpunks mailing list on
+ # September 13, 1994.
+ # http://cypherpunks.venona.com/date/1994/09/msg00420.html
+
+ ('0123456789abcdef', '75b7878099e0c596', '0123456789abcdef',
+ 'Test vector 0'),
+
+ ('0000000000000000', '7494c2e7104b0879', '0123456789abcdef',
+ 'Test vector 1'),
+
+ ('0000000000000000', 'de188941a3375d3a', '0000000000000000',
+ 'Test vector 2'),
+
+ ('00000000000000000000', 'd6a141a7ec3c38dfbd61', 'ef012345',
+ 'Test vector 3'),
+
+ ('01' * 512,
+ '7595c3e6114a09780c4ad452338e1ffd9a1be9498f813d76533449b6778dcad8'
+ + 'c78a8d2ba9ac66085d0e53d59c26c2d1c490c1ebbe0ce66d1b6b1b13b6b919b8'
+ + '47c25a91447a95e75e4ef16779cde8bf0a95850e32af9689444fd377108f98fd'
+ + 'cbd4e726567500990bcc7e0ca3c4aaa304a387d20f3b8fbbcd42a1bd311d7a43'
+ + '03dda5ab078896ae80c18b0af66dff319616eb784e495ad2ce90d7f772a81747'
+ + 'b65f62093b1e0db9e5ba532fafec47508323e671327df9444432cb7367cec82f'
+ + '5d44c0d00b67d650a075cd4b70dedd77eb9b10231b6b5b741347396d62897421'
+ + 'd43df9b42e446e358e9c11a9b2184ecbef0cd8e7a877ef968f1390ec9b3d35a5'
+ + '585cb009290e2fcde7b5ec66d9084be44055a619d9dd7fc3166f9487f7cb2729'
+ + '12426445998514c15d53a18c864ce3a2b7555793988126520eacf2e3066e230c'
+ + '91bee4dd5304f5fd0405b35bd99c73135d3d9bc335ee049ef69b3867bf2d7bd1'
+ + 'eaa595d8bfc0066ff8d31509eb0c6caa006c807a623ef84c3d33c195d23ee320'
+ + 'c40de0558157c822d4b8c569d849aed59d4e0fd7f379586b4b7ff684ed6a189f'
+ + '7486d49b9c4bad9ba24b96abf924372c8a8fffb10d55354900a77a3db5f205e1'
+ + 'b99fcd8660863a159ad4abe40fa48934163ddde542a6585540fd683cbfd8c00f'
+ + '12129a284deacc4cdefe58be7137541c047126c8d49e2755ab181ab7e940b0c0',
+ '0123456789abcdef',
+ "Test vector 4"),
+]
+
+def get_tests(config={}):
+ from Crypto.Cipher import ARC4
+ from common import make_stream_tests
+ return make_stream_tests(ARC4, "ARC4", test_data)
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_Blowfish.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_Blowfish.py
new file mode 100644
index 000000000..e8f73a6ab
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_Blowfish.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/test_Blowfish.py: Self-test for the Blowfish cipher
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.Blowfish"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (plaintext, ciphertext, key) tuples.
+test_data = [
+ # Test vectors from http://www.schneier.com/code/vectors.txt
+ ('0000000000000000', '4ef997456198dd78', '0000000000000000'),
+ ('ffffffffffffffff', '51866fd5b85ecb8a', 'ffffffffffffffff'),
+ ('1000000000000001', '7d856f9a613063f2', '3000000000000000'),
+ ('1111111111111111', '2466dd878b963c9d', '1111111111111111'),
+ ('1111111111111111', '61f9c3802281b096', '0123456789abcdef'),
+ ('0123456789abcdef', '7d0cc630afda1ec7', '1111111111111111'),
+ ('0000000000000000', '4ef997456198dd78', '0000000000000000'),
+ ('0123456789abcdef', '0aceab0fc6a0a28d', 'fedcba9876543210'),
+ ('01a1d6d039776742', '59c68245eb05282b', '7ca110454a1a6e57'),
+ ('5cd54ca83def57da', 'b1b8cc0b250f09a0', '0131d9619dc1376e'),
+ ('0248d43806f67172', '1730e5778bea1da4', '07a1133e4a0b2686'),
+ ('51454b582ddf440a', 'a25e7856cf2651eb', '3849674c2602319e'),
+ ('42fd443059577fa2', '353882b109ce8f1a', '04b915ba43feb5b6'),
+ ('059b5e0851cf143a', '48f4d0884c379918', '0113b970fd34f2ce'),
+ ('0756d8e0774761d2', '432193b78951fc98', '0170f175468fb5e6'),
+ ('762514b829bf486a', '13f04154d69d1ae5', '43297fad38e373fe'),
+ ('3bdd119049372802', '2eedda93ffd39c79', '07a7137045da2a16'),
+ ('26955f6835af609a', 'd887e0393c2da6e3', '04689104c2fd3b2f'),
+ ('164d5e404f275232', '5f99d04f5b163969', '37d06bb516cb7546'),
+ ('6b056e18759f5cca', '4a057a3b24d3977b', '1f08260d1ac2465e'),
+ ('004bd6ef09176062', '452031c1e4fada8e', '584023641aba6176'),
+ ('480d39006ee762f2', '7555ae39f59b87bd', '025816164629b007'),
+ ('437540c8698f3cfa', '53c55f9cb49fc019', '49793ebc79b3258f'),
+ ('072d43a077075292', '7a8e7bfa937e89a3', '4fb05e1515ab73a7'),
+ ('02fe55778117f12a', 'cf9c5d7a4986adb5', '49e95d6d4ca229bf'),
+ ('1d9d5c5018f728c2', 'd1abb290658bc778', '018310dc409b26d6'),
+ ('305532286d6f295a', '55cb3774d13ef201', '1c587f1c13924fef'),
+ ('0123456789abcdef', 'fa34ec4847b268b2', '0101010101010101'),
+ ('0123456789abcdef', 'a790795108ea3cae', '1f1f1f1f0e0e0e0e'),
+ ('0123456789abcdef', 'c39e072d9fac631d', 'e0fee0fef1fef1fe'),
+ ('ffffffffffffffff', '014933e0cdaff6e4', '0000000000000000'),
+ ('0000000000000000', 'f21e9a77b71c49bc', 'ffffffffffffffff'),
+ ('0000000000000000', '245946885754369a', '0123456789abcdef'),
+ ('ffffffffffffffff', '6b5c5a9c5d9e0a5a', 'fedcba9876543210'),
+ ('fedcba9876543210', 'f9ad597c49db005e', 'f0'),
+ ('fedcba9876543210', 'e91d21c1d961a6d6', 'f0e1'),
+ ('fedcba9876543210', 'e9c2b70a1bc65cf3', 'f0e1d2'),
+ ('fedcba9876543210', 'be1e639408640f05', 'f0e1d2c3'),
+ ('fedcba9876543210', 'b39e44481bdb1e6e', 'f0e1d2c3b4'),
+ ('fedcba9876543210', '9457aa83b1928c0d', 'f0e1d2c3b4a5'),
+ ('fedcba9876543210', '8bb77032f960629d', 'f0e1d2c3b4a596'),
+ ('fedcba9876543210', 'e87a244e2cc85e82', 'f0e1d2c3b4a59687'),
+ ('fedcba9876543210', '15750e7a4f4ec577', 'f0e1d2c3b4a5968778'),
+ ('fedcba9876543210', '122ba70b3ab64ae0', 'f0e1d2c3b4a596877869'),
+ ('fedcba9876543210', '3a833c9affc537f6', 'f0e1d2c3b4a5968778695a'),
+ ('fedcba9876543210', '9409da87a90f6bf2', 'f0e1d2c3b4a5968778695a4b'),
+ ('fedcba9876543210', '884f80625060b8b4', 'f0e1d2c3b4a5968778695a4b3c'),
+ ('fedcba9876543210', '1f85031c19e11968', 'f0e1d2c3b4a5968778695a4b3c2d'),
+ ('fedcba9876543210', '79d9373a714ca34f', 'f0e1d2c3b4a5968778695a4b3c2d1e'),
+ ('fedcba9876543210', '93142887ee3be15c',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f'),
+ ('fedcba9876543210', '03429e838ce2d14b',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f00'),
+ ('fedcba9876543210', 'a4299e27469ff67b',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f0011'),
+ ('fedcba9876543210', 'afd5aed1c1bc96a8',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f001122'),
+ ('fedcba9876543210', '10851c0e3858da9f',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f00112233'),
+ ('fedcba9876543210', 'e6f51ed79b9db21f',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f0011223344'),
+ ('fedcba9876543210', '64a6e14afd36b46f',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f001122334455'),
+ ('fedcba9876543210', '80c7d7d45a5479ad',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f00112233445566'),
+ ('fedcba9876543210', '05044b62fa52d080',
+ 'f0e1d2c3b4a5968778695a4b3c2d1e0f0011223344556677'),
+]
+
+def get_tests(config={}):
+ from Crypto.Cipher import Blowfish
+ from common import make_block_tests
+ return make_block_tests(Blowfish, "Blowfish", test_data)
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_CAST.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_CAST.py
new file mode 100644
index 000000000..1cfcec03c
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_CAST.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/CAST.py: Self-test for the CAST-128 (CAST5) cipher
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.CAST"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (plaintext, ciphertext, key) tuples.
+test_data = [
+ # Test vectors from RFC 2144, B.1
+ ('0123456789abcdef', '238b4fe5847e44b2',
+ '0123456712345678234567893456789a',
+ '128-bit key'),
+
+ ('0123456789abcdef', 'eb6a711a2c02271b',
+ '01234567123456782345',
+ '80-bit key'),
+
+ ('0123456789abcdef', '7ac816d16e9b302e',
+ '0123456712',
+ '40-bit key'),
+]
+
+def get_tests(config={}):
+ from Crypto.Cipher import CAST
+ from common import make_block_tests
+ return make_block_tests(CAST, "CAST", test_data)
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES.py
new file mode 100644
index 000000000..c5d114b1e
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES.py
@@ -0,0 +1,339 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/DES.py: Self-test for the (Single) DES cipher
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.DES"""
+
+__revision__ = "$Id$"
+
+from common import dict # For compatibility with Python 2.1 and 2.2
+from Crypto.Util.py3compat import *
+import unittest
+
+# This is a list of (plaintext, ciphertext, key, description) tuples.
+SP800_17_B1_KEY = '01' * 8
+SP800_17_B2_PT = '00' * 8
+test_data = [
+ # Test vectors from Appendix A of NIST SP 800-17
+ # "Modes of Operation Validation System (MOVS): Requirements and Procedures"
+ # http://csrc.nist.gov/publications/nistpubs/800-17/800-17.pdf
+
+ # Appendix A - "Sample Round Outputs for the DES"
+ ('0000000000000000', '82dcbafbdeab6602', '10316e028c8f3b4a',
+ "NIST SP800-17 A"),
+
+ # Table B.1 - Variable Plaintext Known Answer Test
+ ('8000000000000000', '95f8a5e5dd31d900', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #0'),
+ ('4000000000000000', 'dd7f121ca5015619', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #1'),
+ ('2000000000000000', '2e8653104f3834ea', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #2'),
+ ('1000000000000000', '4bd388ff6cd81d4f', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #3'),
+ ('0800000000000000', '20b9e767b2fb1456', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #4'),
+ ('0400000000000000', '55579380d77138ef', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #5'),
+ ('0200000000000000', '6cc5defaaf04512f', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #6'),
+ ('0100000000000000', '0d9f279ba5d87260', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #7'),
+ ('0080000000000000', 'd9031b0271bd5a0a', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #8'),
+ ('0040000000000000', '424250b37c3dd951', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #9'),
+ ('0020000000000000', 'b8061b7ecd9a21e5', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #10'),
+ ('0010000000000000', 'f15d0f286b65bd28', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #11'),
+ ('0008000000000000', 'add0cc8d6e5deba1', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #12'),
+ ('0004000000000000', 'e6d5f82752ad63d1', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #13'),
+ ('0002000000000000', 'ecbfe3bd3f591a5e', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #14'),
+ ('0001000000000000', 'f356834379d165cd', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #15'),
+ ('0000800000000000', '2b9f982f20037fa9', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #16'),
+ ('0000400000000000', '889de068a16f0be6', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #17'),
+ ('0000200000000000', 'e19e275d846a1298', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #18'),
+ ('0000100000000000', '329a8ed523d71aec', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #19'),
+ ('0000080000000000', 'e7fce22557d23c97', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #20'),
+ ('0000040000000000', '12a9f5817ff2d65d', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #21'),
+ ('0000020000000000', 'a484c3ad38dc9c19', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #22'),
+ ('0000010000000000', 'fbe00a8a1ef8ad72', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #23'),
+ ('0000008000000000', '750d079407521363', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #24'),
+ ('0000004000000000', '64feed9c724c2faf', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #25'),
+ ('0000002000000000', 'f02b263b328e2b60', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #26'),
+ ('0000001000000000', '9d64555a9a10b852', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #27'),
+ ('0000000800000000', 'd106ff0bed5255d7', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #28'),
+ ('0000000400000000', 'e1652c6b138c64a5', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #29'),
+ ('0000000200000000', 'e428581186ec8f46', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #30'),
+ ('0000000100000000', 'aeb5f5ede22d1a36', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #31'),
+ ('0000000080000000', 'e943d7568aec0c5c', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #32'),
+ ('0000000040000000', 'df98c8276f54b04b', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #33'),
+ ('0000000020000000', 'b160e4680f6c696f', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #34'),
+ ('0000000010000000', 'fa0752b07d9c4ab8', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #35'),
+ ('0000000008000000', 'ca3a2b036dbc8502', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #36'),
+ ('0000000004000000', '5e0905517bb59bcf', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #37'),
+ ('0000000002000000', '814eeb3b91d90726', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #38'),
+ ('0000000001000000', '4d49db1532919c9f', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #39'),
+ ('0000000000800000', '25eb5fc3f8cf0621', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #40'),
+ ('0000000000400000', 'ab6a20c0620d1c6f', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #41'),
+ ('0000000000200000', '79e90dbc98f92cca', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #42'),
+ ('0000000000100000', '866ecedd8072bb0e', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #43'),
+ ('0000000000080000', '8b54536f2f3e64a8', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #44'),
+ ('0000000000040000', 'ea51d3975595b86b', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #45'),
+ ('0000000000020000', 'caffc6ac4542de31', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #46'),
+ ('0000000000010000', '8dd45a2ddf90796c', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #47'),
+ ('0000000000008000', '1029d55e880ec2d0', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #48'),
+ ('0000000000004000', '5d86cb23639dbea9', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #49'),
+ ('0000000000002000', '1d1ca853ae7c0c5f', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #50'),
+ ('0000000000001000', 'ce332329248f3228', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #51'),
+ ('0000000000000800', '8405d1abe24fb942', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #52'),
+ ('0000000000000400', 'e643d78090ca4207', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #53'),
+ ('0000000000000200', '48221b9937748a23', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #54'),
+ ('0000000000000100', 'dd7c0bbd61fafd54', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #55'),
+ ('0000000000000080', '2fbc291a570db5c4', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #56'),
+ ('0000000000000040', 'e07c30d7e4e26e12', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #57'),
+ ('0000000000000020', '0953e2258e8e90a1', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #58'),
+ ('0000000000000010', '5b711bc4ceebf2ee', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #59'),
+ ('0000000000000008', 'cc083f1e6d9e85f6', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #60'),
+ ('0000000000000004', 'd2fd8867d50d2dfe', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #61'),
+ ('0000000000000002', '06e7ea22ce92708f', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #62'),
+ ('0000000000000001', '166b40b44aba4bd6', SP800_17_B1_KEY,
+ 'NIST SP800-17 B.1 #63'),
+
+ # Table B.2 - Variable Key Known Answer Test
+ (SP800_17_B2_PT, '95a8d72813daa94d', '8001010101010101',
+ 'NIST SP800-17 B.2 #0'),
+ (SP800_17_B2_PT, '0eec1487dd8c26d5', '4001010101010101',
+ 'NIST SP800-17 B.2 #1'),
+ (SP800_17_B2_PT, '7ad16ffb79c45926', '2001010101010101',
+ 'NIST SP800-17 B.2 #2'),
+ (SP800_17_B2_PT, 'd3746294ca6a6cf3', '1001010101010101',
+ 'NIST SP800-17 B.2 #3'),
+ (SP800_17_B2_PT, '809f5f873c1fd761', '0801010101010101',
+ 'NIST SP800-17 B.2 #4'),
+ (SP800_17_B2_PT, 'c02faffec989d1fc', '0401010101010101',
+ 'NIST SP800-17 B.2 #5'),
+ (SP800_17_B2_PT, '4615aa1d33e72f10', '0201010101010101',
+ 'NIST SP800-17 B.2 #6'),
+ (SP800_17_B2_PT, '2055123350c00858', '0180010101010101',
+ 'NIST SP800-17 B.2 #7'),
+ (SP800_17_B2_PT, 'df3b99d6577397c8', '0140010101010101',
+ 'NIST SP800-17 B.2 #8'),
+ (SP800_17_B2_PT, '31fe17369b5288c9', '0120010101010101',
+ 'NIST SP800-17 B.2 #9'),
+ (SP800_17_B2_PT, 'dfdd3cc64dae1642', '0110010101010101',
+ 'NIST SP800-17 B.2 #10'),
+ (SP800_17_B2_PT, '178c83ce2b399d94', '0108010101010101',
+ 'NIST SP800-17 B.2 #11'),
+ (SP800_17_B2_PT, '50f636324a9b7f80', '0104010101010101',
+ 'NIST SP800-17 B.2 #12'),
+ (SP800_17_B2_PT, 'a8468ee3bc18f06d', '0102010101010101',
+ 'NIST SP800-17 B.2 #13'),
+ (SP800_17_B2_PT, 'a2dc9e92fd3cde92', '0101800101010101',
+ 'NIST SP800-17 B.2 #14'),
+ (SP800_17_B2_PT, 'cac09f797d031287', '0101400101010101',
+ 'NIST SP800-17 B.2 #15'),
+ (SP800_17_B2_PT, '90ba680b22aeb525', '0101200101010101',
+ 'NIST SP800-17 B.2 #16'),
+ (SP800_17_B2_PT, 'ce7a24f350e280b6', '0101100101010101',
+ 'NIST SP800-17 B.2 #17'),
+ (SP800_17_B2_PT, '882bff0aa01a0b87', '0101080101010101',
+ 'NIST SP800-17 B.2 #18'),
+ (SP800_17_B2_PT, '25610288924511c2', '0101040101010101',
+ 'NIST SP800-17 B.2 #19'),
+ (SP800_17_B2_PT, 'c71516c29c75d170', '0101020101010101',
+ 'NIST SP800-17 B.2 #20'),
+ (SP800_17_B2_PT, '5199c29a52c9f059', '0101018001010101',
+ 'NIST SP800-17 B.2 #21'),
+ (SP800_17_B2_PT, 'c22f0a294a71f29f', '0101014001010101',
+ 'NIST SP800-17 B.2 #22'),
+ (SP800_17_B2_PT, 'ee371483714c02ea', '0101012001010101',
+ 'NIST SP800-17 B.2 #23'),
+ (SP800_17_B2_PT, 'a81fbd448f9e522f', '0101011001010101',
+ 'NIST SP800-17 B.2 #24'),
+ (SP800_17_B2_PT, '4f644c92e192dfed', '0101010801010101',
+ 'NIST SP800-17 B.2 #25'),
+ (SP800_17_B2_PT, '1afa9a66a6df92ae', '0101010401010101',
+ 'NIST SP800-17 B.2 #26'),
+ (SP800_17_B2_PT, 'b3c1cc715cb879d8', '0101010201010101',
+ 'NIST SP800-17 B.2 #27'),
+ (SP800_17_B2_PT, '19d032e64ab0bd8b', '0101010180010101',
+ 'NIST SP800-17 B.2 #28'),
+ (SP800_17_B2_PT, '3cfaa7a7dc8720dc', '0101010140010101',
+ 'NIST SP800-17 B.2 #29'),
+ (SP800_17_B2_PT, 'b7265f7f447ac6f3', '0101010120010101',
+ 'NIST SP800-17 B.2 #30'),
+ (SP800_17_B2_PT, '9db73b3c0d163f54', '0101010110010101',
+ 'NIST SP800-17 B.2 #31'),
+ (SP800_17_B2_PT, '8181b65babf4a975', '0101010108010101',
+ 'NIST SP800-17 B.2 #32'),
+ (SP800_17_B2_PT, '93c9b64042eaa240', '0101010104010101',
+ 'NIST SP800-17 B.2 #33'),
+ (SP800_17_B2_PT, '5570530829705592', '0101010102010101',
+ 'NIST SP800-17 B.2 #34'),
+ (SP800_17_B2_PT, '8638809e878787a0', '0101010101800101',
+ 'NIST SP800-17 B.2 #35'),
+ (SP800_17_B2_PT, '41b9a79af79ac208', '0101010101400101',
+ 'NIST SP800-17 B.2 #36'),
+ (SP800_17_B2_PT, '7a9be42f2009a892', '0101010101200101',
+ 'NIST SP800-17 B.2 #37'),
+ (SP800_17_B2_PT, '29038d56ba6d2745', '0101010101100101',
+ 'NIST SP800-17 B.2 #38'),
+ (SP800_17_B2_PT, '5495c6abf1e5df51', '0101010101080101',
+ 'NIST SP800-17 B.2 #39'),
+ (SP800_17_B2_PT, 'ae13dbd561488933', '0101010101040101',
+ 'NIST SP800-17 B.2 #40'),
+ (SP800_17_B2_PT, '024d1ffa8904e389', '0101010101020101',
+ 'NIST SP800-17 B.2 #41'),
+ (SP800_17_B2_PT, 'd1399712f99bf02e', '0101010101018001',
+ 'NIST SP800-17 B.2 #42'),
+ (SP800_17_B2_PT, '14c1d7c1cffec79e', '0101010101014001',
+ 'NIST SP800-17 B.2 #43'),
+ (SP800_17_B2_PT, '1de5279dae3bed6f', '0101010101012001',
+ 'NIST SP800-17 B.2 #44'),
+ (SP800_17_B2_PT, 'e941a33f85501303', '0101010101011001',
+ 'NIST SP800-17 B.2 #45'),
+ (SP800_17_B2_PT, 'da99dbbc9a03f379', '0101010101010801',
+ 'NIST SP800-17 B.2 #46'),
+ (SP800_17_B2_PT, 'b7fc92f91d8e92e9', '0101010101010401',
+ 'NIST SP800-17 B.2 #47'),
+ (SP800_17_B2_PT, 'ae8e5caa3ca04e85', '0101010101010201',
+ 'NIST SP800-17 B.2 #48'),
+ (SP800_17_B2_PT, '9cc62df43b6eed74', '0101010101010180',
+ 'NIST SP800-17 B.2 #49'),
+ (SP800_17_B2_PT, 'd863dbb5c59a91a0', '0101010101010140',
+ 'NIST SP800-17 B.2 #50'),
+ (SP800_17_B2_PT, 'a1ab2190545b91d7', '0101010101010120',
+ 'NIST SP800-17 B.2 #51'),
+ (SP800_17_B2_PT, '0875041e64c570f7', '0101010101010110',
+ 'NIST SP800-17 B.2 #52'),
+ (SP800_17_B2_PT, '5a594528bebef1cc', '0101010101010108',
+ 'NIST SP800-17 B.2 #53'),
+ (SP800_17_B2_PT, 'fcdb3291de21f0c0', '0101010101010104',
+ 'NIST SP800-17 B.2 #54'),
+ (SP800_17_B2_PT, '869efd7f9f265a09', '0101010101010102',
+ 'NIST SP800-17 B.2 #55'),
+]
+
+class RonRivestTest(unittest.TestCase):
+ """ Ronald L. Rivest's DES test, see
+ http://people.csail.mit.edu/rivest/Destest.txt
+ ABSTRACT
+ --------
+
+ We present a simple way to test the correctness of a DES implementation:
+ Use the recurrence relation:
+
+ X0 = 9474B8E8C73BCA7D (hexadecimal)
+
+ X(i+1) = IF (i is even) THEN E(Xi,Xi) ELSE D(Xi,Xi)
+
+ to compute a sequence of 64-bit values: X0, X1, X2, ..., X16. Here
+ E(X,K) denotes the DES encryption of X using key K, and D(X,K) denotes
+ the DES decryption of X using key K. If you obtain
+
+ X16 = 1B1A2DDB4C642438
+
+ your implementation does not have any of the 36,568 possible single-fault
+ errors described herein.
+ """
+ def runTest(self):
+ from Crypto.Cipher import DES
+ from binascii import b2a_hex
+
+ X = []
+ X[0:] = [b('\x94\x74\xB8\xE8\xC7\x3B\xCA\x7D')]
+
+ for i in range(16):
+ c = DES.new(X[i],DES.MODE_ECB)
+ if not (i&1): # (num&1) returns 1 for odd numbers
+ X[i+1:] = [c.encrypt(X[i])] # even
+ else:
+ X[i+1:] = [c.decrypt(X[i])] # odd
+
+ self.assertEqual(b2a_hex(X[16]),
+ b2a_hex(b('\x1B\x1A\x2D\xDB\x4C\x64\x24\x38')))
+
+def get_tests(config={}):
+ from Crypto.Cipher import DES
+ from common import make_block_tests
+ return make_block_tests(DES, "DES", test_data) + [RonRivestTest()]
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES3.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES3.py
new file mode 100644
index 000000000..50e969b9d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_DES3.py
@@ -0,0 +1,333 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/DES3.py: Self-test for the Triple-DES cipher
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.DES3"""
+
+__revision__ = "$Id$"
+
+from common import dict # For compatibility with Python 2.1 and 2.2
+from Crypto.Util.py3compat import *
+from binascii import hexlify
+
+# This is a list of (plaintext, ciphertext, key, description) tuples.
+SP800_20_A1_KEY = '01' * 24
+SP800_20_A2_PT = '00' * 8
+test_data = [
+ # Test vector from Appendix B of NIST SP 800-67
+ # "Recommendation for the Triple Data Encryption Algorithm (TDEA) Block
+ # Cipher"
+ # http://csrc.nist.gov/publications/nistpubs/800-67/SP800-67.pdf
+ ('54686520717566636b2062726f776e20666f78206a756d70',
+ 'a826fd8ce53b855fcce21c8112256fe668d5c05dd9b6b900',
+ '0123456789abcdef23456789abcdef01456789abcdef0123',
+ 'NIST SP800-67 B.1'),
+
+ # Test vectors "The Multi-block Message Test (MMT) for DES and TDES"
+ # http://csrc.nist.gov/groups/STM/cavp/documents/des/DESMMT.pdf
+ ('326a494cd33fe756', 'b22b8d66de970692',
+ '627f460e08104a1043cd265d5840eaf1313edf97df2a8a8c',
+ 'DESMMT #1', dict(mode='CBC', iv='8e29f75ea77e5475')),
+
+ ('84401f78fe6c10876d8ea23094ea5309', '7b1f7c7e3b1c948ebd04a75ffba7d2f5',
+ '37ae5ebf46dff2dc0754b94f31cbb3855e7fd36dc870bfae',
+ 'DESMMT #2', dict(mode='CBC', iv='3d1de3cc132e3b65')),
+
+ # Test vectors from Appendix A of NIST SP 800-20
+ # "Modes of Operation Validation System for the Triple Data Encryption
+ # Algorithm (TMOVS): Requirements and Procedures"
+ # http://csrc.nist.gov/publications/nistpubs/800-20/800-20.pdf
+
+ # Table A.1 - Variable Plaintext Known Answer Test
+ ('8000000000000000', '95f8a5e5dd31d900', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #0'),
+ ('4000000000000000', 'dd7f121ca5015619', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #1'),
+ ('2000000000000000', '2e8653104f3834ea', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #2'),
+ ('1000000000000000', '4bd388ff6cd81d4f', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #3'),
+ ('0800000000000000', '20b9e767b2fb1456', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #4'),
+ ('0400000000000000', '55579380d77138ef', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #5'),
+ ('0200000000000000', '6cc5defaaf04512f', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #6'),
+ ('0100000000000000', '0d9f279ba5d87260', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #7'),
+ ('0080000000000000', 'd9031b0271bd5a0a', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #8'),
+ ('0040000000000000', '424250b37c3dd951', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #9'),
+ ('0020000000000000', 'b8061b7ecd9a21e5', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #10'),
+ ('0010000000000000', 'f15d0f286b65bd28', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #11'),
+ ('0008000000000000', 'add0cc8d6e5deba1', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #12'),
+ ('0004000000000000', 'e6d5f82752ad63d1', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #13'),
+ ('0002000000000000', 'ecbfe3bd3f591a5e', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #14'),
+ ('0001000000000000', 'f356834379d165cd', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #15'),
+ ('0000800000000000', '2b9f982f20037fa9', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #16'),
+ ('0000400000000000', '889de068a16f0be6', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #17'),
+ ('0000200000000000', 'e19e275d846a1298', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #18'),
+ ('0000100000000000', '329a8ed523d71aec', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #19'),
+ ('0000080000000000', 'e7fce22557d23c97', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #20'),
+ ('0000040000000000', '12a9f5817ff2d65d', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #21'),
+ ('0000020000000000', 'a484c3ad38dc9c19', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #22'),
+ ('0000010000000000', 'fbe00a8a1ef8ad72', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #23'),
+ ('0000008000000000', '750d079407521363', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #24'),
+ ('0000004000000000', '64feed9c724c2faf', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #25'),
+ ('0000002000000000', 'f02b263b328e2b60', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #26'),
+ ('0000001000000000', '9d64555a9a10b852', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #27'),
+ ('0000000800000000', 'd106ff0bed5255d7', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #28'),
+ ('0000000400000000', 'e1652c6b138c64a5', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #29'),
+ ('0000000200000000', 'e428581186ec8f46', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #30'),
+ ('0000000100000000', 'aeb5f5ede22d1a36', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #31'),
+ ('0000000080000000', 'e943d7568aec0c5c', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #32'),
+ ('0000000040000000', 'df98c8276f54b04b', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #33'),
+ ('0000000020000000', 'b160e4680f6c696f', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #34'),
+ ('0000000010000000', 'fa0752b07d9c4ab8', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #35'),
+ ('0000000008000000', 'ca3a2b036dbc8502', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #36'),
+ ('0000000004000000', '5e0905517bb59bcf', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #37'),
+ ('0000000002000000', '814eeb3b91d90726', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #38'),
+ ('0000000001000000', '4d49db1532919c9f', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #39'),
+ ('0000000000800000', '25eb5fc3f8cf0621', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #40'),
+ ('0000000000400000', 'ab6a20c0620d1c6f', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #41'),
+ ('0000000000200000', '79e90dbc98f92cca', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #42'),
+ ('0000000000100000', '866ecedd8072bb0e', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #43'),
+ ('0000000000080000', '8b54536f2f3e64a8', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #44'),
+ ('0000000000040000', 'ea51d3975595b86b', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #45'),
+ ('0000000000020000', 'caffc6ac4542de31', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #46'),
+ ('0000000000010000', '8dd45a2ddf90796c', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #47'),
+ ('0000000000008000', '1029d55e880ec2d0', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #48'),
+ ('0000000000004000', '5d86cb23639dbea9', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #49'),
+ ('0000000000002000', '1d1ca853ae7c0c5f', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #50'),
+ ('0000000000001000', 'ce332329248f3228', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #51'),
+ ('0000000000000800', '8405d1abe24fb942', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #52'),
+ ('0000000000000400', 'e643d78090ca4207', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #53'),
+ ('0000000000000200', '48221b9937748a23', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #54'),
+ ('0000000000000100', 'dd7c0bbd61fafd54', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #55'),
+ ('0000000000000080', '2fbc291a570db5c4', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #56'),
+ ('0000000000000040', 'e07c30d7e4e26e12', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #57'),
+ ('0000000000000020', '0953e2258e8e90a1', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #58'),
+ ('0000000000000010', '5b711bc4ceebf2ee', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #59'),
+ ('0000000000000008', 'cc083f1e6d9e85f6', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #60'),
+ ('0000000000000004', 'd2fd8867d50d2dfe', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #61'),
+ ('0000000000000002', '06e7ea22ce92708f', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #62'),
+ ('0000000000000001', '166b40b44aba4bd6', SP800_20_A1_KEY,
+ 'NIST SP800-20 A.1 #63'),
+
+ # Table A.2 - Variable Key Known Answer Test
+ (SP800_20_A2_PT, '95a8d72813daa94d', '8001010101010101'*3,
+ 'NIST SP800-20 A.2 #0'),
+ (SP800_20_A2_PT, '0eec1487dd8c26d5', '4001010101010101'*3,
+ 'NIST SP800-20 A.2 #1'),
+ (SP800_20_A2_PT, '7ad16ffb79c45926', '2001010101010101'*3,
+ 'NIST SP800-20 A.2 #2'),
+ (SP800_20_A2_PT, 'd3746294ca6a6cf3', '1001010101010101'*3,
+ 'NIST SP800-20 A.2 #3'),
+ (SP800_20_A2_PT, '809f5f873c1fd761', '0801010101010101'*3,
+ 'NIST SP800-20 A.2 #4'),
+ (SP800_20_A2_PT, 'c02faffec989d1fc', '0401010101010101'*3,
+ 'NIST SP800-20 A.2 #5'),
+ (SP800_20_A2_PT, '4615aa1d33e72f10', '0201010101010101'*3,
+ 'NIST SP800-20 A.2 #6'),
+ (SP800_20_A2_PT, '2055123350c00858', '0180010101010101'*3,
+ 'NIST SP800-20 A.2 #7'),
+ (SP800_20_A2_PT, 'df3b99d6577397c8', '0140010101010101'*3,
+ 'NIST SP800-20 A.2 #8'),
+ (SP800_20_A2_PT, '31fe17369b5288c9', '0120010101010101'*3,
+ 'NIST SP800-20 A.2 #9'),
+ (SP800_20_A2_PT, 'dfdd3cc64dae1642', '0110010101010101'*3,
+ 'NIST SP800-20 A.2 #10'),
+ (SP800_20_A2_PT, '178c83ce2b399d94', '0108010101010101'*3,
+ 'NIST SP800-20 A.2 #11'),
+ (SP800_20_A2_PT, '50f636324a9b7f80', '0104010101010101'*3,
+ 'NIST SP800-20 A.2 #12'),
+ (SP800_20_A2_PT, 'a8468ee3bc18f06d', '0102010101010101'*3,
+ 'NIST SP800-20 A.2 #13'),
+ (SP800_20_A2_PT, 'a2dc9e92fd3cde92', '0101800101010101'*3,
+ 'NIST SP800-20 A.2 #14'),
+ (SP800_20_A2_PT, 'cac09f797d031287', '0101400101010101'*3,
+ 'NIST SP800-20 A.2 #15'),
+ (SP800_20_A2_PT, '90ba680b22aeb525', '0101200101010101'*3,
+ 'NIST SP800-20 A.2 #16'),
+ (SP800_20_A2_PT, 'ce7a24f350e280b6', '0101100101010101'*3,
+ 'NIST SP800-20 A.2 #17'),
+ (SP800_20_A2_PT, '882bff0aa01a0b87', '0101080101010101'*3,
+ 'NIST SP800-20 A.2 #18'),
+ (SP800_20_A2_PT, '25610288924511c2', '0101040101010101'*3,
+ 'NIST SP800-20 A.2 #19'),
+ (SP800_20_A2_PT, 'c71516c29c75d170', '0101020101010101'*3,
+ 'NIST SP800-20 A.2 #20'),
+ (SP800_20_A2_PT, '5199c29a52c9f059', '0101018001010101'*3,
+ 'NIST SP800-20 A.2 #21'),
+ (SP800_20_A2_PT, 'c22f0a294a71f29f', '0101014001010101'*3,
+ 'NIST SP800-20 A.2 #22'),
+ (SP800_20_A2_PT, 'ee371483714c02ea', '0101012001010101'*3,
+ 'NIST SP800-20 A.2 #23'),
+ (SP800_20_A2_PT, 'a81fbd448f9e522f', '0101011001010101'*3,
+ 'NIST SP800-20 A.2 #24'),
+ (SP800_20_A2_PT, '4f644c92e192dfed', '0101010801010101'*3,
+ 'NIST SP800-20 A.2 #25'),
+ (SP800_20_A2_PT, '1afa9a66a6df92ae', '0101010401010101'*3,
+ 'NIST SP800-20 A.2 #26'),
+ (SP800_20_A2_PT, 'b3c1cc715cb879d8', '0101010201010101'*3,
+ 'NIST SP800-20 A.2 #27'),
+ (SP800_20_A2_PT, '19d032e64ab0bd8b', '0101010180010101'*3,
+ 'NIST SP800-20 A.2 #28'),
+ (SP800_20_A2_PT, '3cfaa7a7dc8720dc', '0101010140010101'*3,
+ 'NIST SP800-20 A.2 #29'),
+ (SP800_20_A2_PT, 'b7265f7f447ac6f3', '0101010120010101'*3,
+ 'NIST SP800-20 A.2 #30'),
+ (SP800_20_A2_PT, '9db73b3c0d163f54', '0101010110010101'*3,
+ 'NIST SP800-20 A.2 #31'),
+ (SP800_20_A2_PT, '8181b65babf4a975', '0101010108010101'*3,
+ 'NIST SP800-20 A.2 #32'),
+ (SP800_20_A2_PT, '93c9b64042eaa240', '0101010104010101'*3,
+ 'NIST SP800-20 A.2 #33'),
+ (SP800_20_A2_PT, '5570530829705592', '0101010102010101'*3,
+ 'NIST SP800-20 A.2 #34'),
+ (SP800_20_A2_PT, '8638809e878787a0', '0101010101800101'*3,
+ 'NIST SP800-20 A.2 #35'),
+ (SP800_20_A2_PT, '41b9a79af79ac208', '0101010101400101'*3,
+ 'NIST SP800-20 A.2 #36'),
+ (SP800_20_A2_PT, '7a9be42f2009a892', '0101010101200101'*3,
+ 'NIST SP800-20 A.2 #37'),
+ (SP800_20_A2_PT, '29038d56ba6d2745', '0101010101100101'*3,
+ 'NIST SP800-20 A.2 #38'),
+ (SP800_20_A2_PT, '5495c6abf1e5df51', '0101010101080101'*3,
+ 'NIST SP800-20 A.2 #39'),
+ (SP800_20_A2_PT, 'ae13dbd561488933', '0101010101040101'*3,
+ 'NIST SP800-20 A.2 #40'),
+ (SP800_20_A2_PT, '024d1ffa8904e389', '0101010101020101'*3,
+ 'NIST SP800-20 A.2 #41'),
+ (SP800_20_A2_PT, 'd1399712f99bf02e', '0101010101018001'*3,
+ 'NIST SP800-20 A.2 #42'),
+ (SP800_20_A2_PT, '14c1d7c1cffec79e', '0101010101014001'*3,
+ 'NIST SP800-20 A.2 #43'),
+ (SP800_20_A2_PT, '1de5279dae3bed6f', '0101010101012001'*3,
+ 'NIST SP800-20 A.2 #44'),
+ (SP800_20_A2_PT, 'e941a33f85501303', '0101010101011001'*3,
+ 'NIST SP800-20 A.2 #45'),
+ (SP800_20_A2_PT, 'da99dbbc9a03f379', '0101010101010801'*3,
+ 'NIST SP800-20 A.2 #46'),
+ (SP800_20_A2_PT, 'b7fc92f91d8e92e9', '0101010101010401'*3,
+ 'NIST SP800-20 A.2 #47'),
+ (SP800_20_A2_PT, 'ae8e5caa3ca04e85', '0101010101010201'*3,
+ 'NIST SP800-20 A.2 #48'),
+ (SP800_20_A2_PT, '9cc62df43b6eed74', '0101010101010180'*3,
+ 'NIST SP800-20 A.2 #49'),
+ (SP800_20_A2_PT, 'd863dbb5c59a91a0', '0101010101010140'*3,
+ 'NIST SP800-20 A.2 #50'),
+ (SP800_20_A2_PT, 'a1ab2190545b91d7', '0101010101010120'*3,
+ 'NIST SP800-20 A.2 #51'),
+ (SP800_20_A2_PT, '0875041e64c570f7', '0101010101010110'*3,
+ 'NIST SP800-20 A.2 #52'),
+ (SP800_20_A2_PT, '5a594528bebef1cc', '0101010101010108'*3,
+ 'NIST SP800-20 A.2 #53'),
+ (SP800_20_A2_PT, 'fcdb3291de21f0c0', '0101010101010104'*3,
+ 'NIST SP800-20 A.2 #54'),
+ (SP800_20_A2_PT, '869efd7f9f265a09', '0101010101010102'*3,
+ 'NIST SP800-20 A.2 #55'),
+
+ # "Two-key 3DES". Test vector generated using PyCrypto 2.0.1.
+ # This test is designed to test the DES3 API, not the correctness of the
+ # output.
+ ('21e81b7ade88a259', '5c577d4d9b20c0f8',
+ '9b397ebf81b1181e282f4bb8adbadc6b', 'Two-key 3DES'),
+
+ # The following test vectors have been generated with gpg v1.4.0.
+ # The command line used was:
+ # gpg -c -z 0 --cipher-algo 3DES --passphrase secret_passphrase \
+ # --disable-mdc --s2k-mode 0 --output ct pt
+ # For an explanation, see test_AES.py .
+ ( 'ac1762037074324fb53ba3596f73656d69746556616c6c6579', # Plaintext, 'YosemiteValley'
+ '9979238528357b90e2e0be549cb0b2d5999b9a4a447e5c5c7d', # Ciphertext
+ '7ade65b460f5ea9be35f9e14aa883a2048e3824aa616c0b2', # Key (hash of 'BearsAhead')
+ 'GPG Test Vector #1',
+ dict(mode='OPENPGP', iv='cd47e2afb8b7e4b0', encrypted_iv='6a7eef0b58050e8b904a' ) ),
+]
+
+def get_tests(config={}):
+ from Crypto.Cipher import DES3
+ from common import make_block_tests
+ return make_block_tests(DES3, "DES3", test_data)
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_XOR.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_XOR.py
new file mode 100644
index 000000000..a4d542aca
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_XOR.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/XOR.py: Self-test for the XOR "cipher"
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Cipher.XOR"""
+
+import unittest
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (plaintext, ciphertext, key) tuples.
+test_data = [
+ # Test vectors written from scratch. (Nobody posts XOR test vectors on the web? How disappointing.)
+ ('01', '01',
+ '00',
+ 'zero key'),
+
+ ('0102040810204080', '0003050911214181',
+ '01',
+ '1-byte key'),
+
+ ('0102040810204080', 'cda8c8a2dc8a8c2a',
+ 'ccaa',
+ '2-byte key'),
+
+ ('ff'*64, 'fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0'*2,
+ '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+ '32-byte key'),
+]
+
+class TruncationSelfTest(unittest.TestCase):
+
+ def runTest(self):
+ """33-byte key (should raise ValueError under current implementation)"""
+ # Crypto.Cipher.XOR previously truncated its inputs at 32 bytes. Now
+ # it should raise a ValueError if the length is too long.
+ self.assertRaises(ValueError, XOR.new, "x"*33)
+
+def get_tests(config={}):
+ global XOR
+ from Crypto.Cipher import XOR
+ from common import make_stream_tests
+ return make_stream_tests(XOR, "XOR", test_data) + [TruncationSelfTest()]
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py
new file mode 100644
index 000000000..7aa17033b
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/test_pkcs1_15.py: Self-test for PKCS#1 v1.5 encryption
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import unittest
+import sys
+
+from Crypto.PublicKey import RSA
+from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex
+from Crypto import Random
+from Crypto.Cipher import PKCS1_v1_5 as PKCS
+from Crypto.Util.py3compat import *
+
+def rws(t):
+ """Remove white spaces, tabs, and new lines from a string"""
+ for c in ['\n', '\t', ' ']:
+ t = t.replace(c,'')
+ return t
+
+def t2b(t):
+ """Convert a text string with bytes in hex form to a byte string"""
+ clean = b(rws(t))
+ if len(clean)%2 == 1:
+ print clean
+ raise ValueError("Even number of characters expected")
+ return a2b_hex(clean)
+
+class PKCS1_15_Tests(unittest.TestCase):
+
+ def setUp(self):
+ self.rng = Random.new().read
+ self.key1024 = RSA.generate(1024, self.rng)
+
+ # List of tuples with test data for PKCS#1 v1.5.
+ # Each tuple is made up by:
+ # Item #0: dictionary with RSA key component, or key to import
+ # Item #1: plaintext
+ # Item #2: ciphertext
+ # Item #3: random data
+
+ _testData = (
+
+ #
+ # Generated with openssl 0.9.8o
+ #
+ (
+ # Private key
+ '''-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDAiAnvIAOvqVwJTaYzsKnefZftgtXGE2hPJppGsWl78yz9jeXY
+W/FxX/gTPURArNhdnhP6n3p2ZaDIBrO2zizbgIXs0IsljTTcr4vnI8fMXzyNUOjA
+zP3nzMqZDZK6757XQAobOssMkBFqRWwilT/3DsBhRpl3iMUhF+wvpTSHewIDAQAB
+AoGAC4HV/inOrpgTvSab8Wj0riyZgQOZ3U3ZpSlsfR8ra9Ib9Uee3jCYnKscu6Gk
+y6zI/cdt8EPJ4PuwAWSNJzbpbVaDvUq25OD+CX8/uRT08yBS4J8TzBitZJTD4lS7
+atdTnKT0Wmwk+u8tDbhvMKwnUHdJLcuIsycts9rwJVapUtkCQQDvDpx2JMun0YKG
+uUttjmL8oJ3U0m3ZvMdVwBecA0eebZb1l2J5PvI3EJD97eKe91Nsw8T3lwpoN40k
+IocSVDklAkEAzi1HLHE6EzVPOe5+Y0kGvrIYRRhncOb72vCvBZvD6wLZpQgqo6c4
+d3XHFBBQWA6xcvQb5w+VVEJZzw64y25sHwJBAMYReRl6SzL0qA0wIYrYWrOt8JeQ
+8mthulcWHXmqTgC6FEXP9Es5GD7/fuKl4wqLKZgIbH4nqvvGay7xXLCXD/ECQH9a
+1JYNMtRen5unSAbIOxRcKkWz92F0LKpm9ZW/S9vFHO+mBcClMGoKJHiuQxLBsLbT
+NtEZfSJZAeS2sUtn3/0CQDb2M2zNBTF8LlM0nxmh0k9VGm5TVIyBEMcipmvOgqIs
+HKukWBcq9f/UOmS0oEhai/6g+Uf7VHJdWaeO5LzuvwU=
+-----END RSA PRIVATE KEY-----''',
+ # Plaintext
+ '''THIS IS PLAINTEXT\x0A''',
+ # Ciphertext
+ '''3f dc fd 3c cd 5c 9b 12 af 65 32 e3 f7 d0 da 36
+ 8f 8f d9 e3 13 1c 7f c8 b3 f9 c1 08 e4 eb 79 9c
+ 91 89 1f 96 3b 94 77 61 99 a4 b1 ee 5d e6 17 c9
+ 5d 0a b5 63 52 0a eb 00 45 38 2a fb b0 71 3d 11
+ f7 a1 9e a7 69 b3 af 61 c0 bb 04 5b 5d 4b 27 44
+ 1f 5b 97 89 ba 6a 08 95 ee 4f a2 eb 56 64 e5 0f
+ da 7c f9 9a 61 61 06 62 ed a0 bc 5f aa 6c 31 78
+ 70 28 1a bb 98 3c e3 6a 60 3c d1 0b 0f 5a f4 75''',
+ # Random data
+ '''eb d7 7d 86 a4 35 23 a3 54 7e 02 0b 42 1d
+ 61 6c af 67 b8 4e 17 56 80 66 36 04 64 34 26 8a
+ 47 dd 44 b3 1a b2 17 60 f4 91 2e e2 b5 95 64 cc
+ f9 da c8 70 94 54 86 4c ef 5b 08 7d 18 c4 ab 8d
+ 04 06 33 8f ca 15 5f 52 60 8a a1 0c f5 08 b5 4c
+ bb 99 b8 94 25 04 9c e6 01 75 e6 f9 63 7a 65 61
+ 13 8a a7 47 77 81 ae 0d b8 2c 4d 50 a5'''
+ ),
+ )
+
+ def testEncrypt1(self):
+ for test in self._testData:
+ # Build the key
+ key = RSA.importKey(test[0])
+ # RNG that takes its random numbers from a pool given
+ # at initialization
+ class randGen:
+ def __init__(self, data):
+ self.data = data
+ self.idx = 0
+ def __call__(self, N):
+ r = self.data[self.idx:N]
+ self.idx += N
+ return r
+ # The real test
+ key._randfunc = randGen(t2b(test[3]))
+ cipher = PKCS.new(key)
+ ct = cipher.encrypt(b(test[1]))
+ self.assertEqual(ct, t2b(test[2]))
+
+ def testEncrypt2(self):
+ # Verify that encryption fail if plaintext is too long
+ pt = '\x00'*(128-11+1)
+ cipher = PKCS.new(self.key1024)
+ self.assertRaises(ValueError, cipher.encrypt, pt)
+
+ def testVerify1(self):
+ for test in self._testData:
+ # Build the key
+ key = RSA.importKey(test[0])
+ # The real test
+ cipher = PKCS.new(key)
+ pt = cipher.decrypt(t2b(test[2]), "---")
+ self.assertEqual(pt, b(test[1]))
+
+ def testVerify2(self):
+ # Verify that decryption fails if ciphertext is not as long as
+ # RSA modulus
+ cipher = PKCS.new(self.key1024)
+ self.assertRaises(ValueError, cipher.decrypt, '\x00'*127, "---")
+ self.assertRaises(ValueError, cipher.decrypt, '\x00'*129, "---")
+
+ # Verify that decryption fails if there are less then 8 non-zero padding
+ # bytes
+ pt = b('\x00\x02' + '\xFF'*7 + '\x00' + '\x45'*118)
+ ct = self.key1024.encrypt(pt, 0)[0]
+ ct = b('\x00'*(128-len(ct))) + ct
+ self.assertEqual("---", cipher.decrypt(ct, "---"))
+
+ def testEncryptVerify1(self):
+ # Encrypt/Verify messages of length [0..RSAlen-11]
+ # and therefore padding [8..117]
+ for pt_len in xrange(0,128-11+1):
+ pt = self.rng(pt_len)
+ cipher = PKCS.new(self.key1024)
+ ct = cipher.encrypt(pt)
+ pt2 = cipher.decrypt(ct, "---")
+ self.assertEqual(pt,pt2)
+
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(PKCS1_15_Tests)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py
new file mode 100644
index 000000000..accca6132
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py
@@ -0,0 +1,372 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Cipher/test_pkcs1_oaep.py: Self-test for PKCS#1 OAEP encryption
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+from __future__ import nested_scopes
+
+__revision__ = "$Id$"
+
+import unittest
+
+from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex
+
+from Crypto.Util.py3compat import *
+from Crypto.PublicKey import RSA
+from Crypto.Cipher import PKCS1_OAEP as PKCS
+from Crypto.Hash import MD2,MD5,SHA as SHA1,SHA256,RIPEMD
+from Crypto import Random
+
+def rws(t):
+ """Remove white spaces, tabs, and new lines from a string"""
+ for c in ['\n', '\t', ' ']:
+ t = t.replace(c,'')
+ return t
+
+def t2b(t):
+ """Convert a text string with bytes in hex form to a byte string"""
+ clean = rws(t)
+ if len(clean)%2 == 1:
+ raise ValueError("Even number of characters expected")
+ return a2b_hex(clean)
+
+class PKCS1_OAEP_Tests(unittest.TestCase):
+
+ def setUp(self):
+ self.rng = Random.new().read
+ self.key1024 = RSA.generate(1024, self.rng)
+
+ # List of tuples with test data for PKCS#1 OAEP
+ # Each tuple is made up by:
+ # Item #0: dictionary with RSA key component
+ # Item #1: plaintext
+ # Item #2: ciphertext
+ # Item #3: random data (=seed)
+ # Item #4: hash object
+
+ _testData = (
+
+ #
+ # From in oaep-int.txt to be found in
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''bb f8 2f 09 06 82 ce 9c 23 38 ac 2b 9d a8 71 f7
+ 36 8d 07 ee d4 10 43 a4 40 d6 b6 f0 74 54 f5 1f
+ b8 df ba af 03 5c 02 ab 61 ea 48 ce eb 6f cd 48
+ 76 ed 52 0d 60 e1 ec 46 19 71 9d 8a 5b 8b 80 7f
+ af b8 e0 a3 df c7 37 72 3e e6 b4 b7 d9 3a 25 84
+ ee 6a 64 9d 06 09 53 74 88 34 b2 45 45 98 39 4e
+ e0 aa b1 2d 7b 61 a5 1f 52 7a 9a 41 f6 c1 68 7f
+ e2 53 72 98 ca 2a 8f 59 46 f8 e5 fd 09 1d bd cb''',
+ # Public key
+ 'e':'11',
+ # In the test vector, only p and q were given...
+ # d is computed offline as e^{-1} mod (p-1)(q-1)
+ 'd':'''a5dafc5341faf289c4b988db30c1cdf83f31251e0
+ 668b42784813801579641b29410b3c7998d6bc465745e5c3
+ 92669d6870da2c082a939e37fdcb82ec93edac97ff3ad595
+ 0accfbc111c76f1a9529444e56aaf68c56c092cd38dc3bef
+ 5d20a939926ed4f74a13eddfbe1a1cecc4894af9428c2b7b
+ 8883fe4463a4bc85b1cb3c1'''
+ }
+ ,
+ # Plaintext
+ '''d4 36 e9 95 69 fd 32 a7 c8 a0 5b bc 90 d3 2c 49''',
+ # Ciphertext
+ '''12 53 e0 4d c0 a5 39 7b b4 4a 7a b8 7e 9b f2 a0
+ 39 a3 3d 1e 99 6f c8 2a 94 cc d3 00 74 c9 5d f7
+ 63 72 20 17 06 9e 52 68 da 5d 1c 0b 4f 87 2c f6
+ 53 c1 1d f8 23 14 a6 79 68 df ea e2 8d ef 04 bb
+ 6d 84 b1 c3 1d 65 4a 19 70 e5 78 3b d6 eb 96 a0
+ 24 c2 ca 2f 4a 90 fe 9f 2e f5 c9 c1 40 e5 bb 48
+ da 95 36 ad 87 00 c8 4f c9 13 0a de a7 4e 55 8d
+ 51 a7 4d df 85 d8 b5 0d e9 68 38 d6 06 3e 09 55''',
+ # Random
+ '''aa fd 12 f6 59 ca e6 34 89 b4 79 e5 07 6d de c2
+ f0 6c b5 8f''',
+ # Hash
+ SHA1,
+ ),
+
+ #
+ # From in oaep-vect.txt to be found in Example 1.1
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''a8 b3 b2 84 af 8e b5 0b 38 70 34 a8 60 f1 46 c4
+ 91 9f 31 87 63 cd 6c 55 98 c8 ae 48 11 a1 e0 ab
+ c4 c7 e0 b0 82 d6 93 a5 e7 fc ed 67 5c f4 66 85
+ 12 77 2c 0c bc 64 a7 42 c6 c6 30 f5 33 c8 cc 72
+ f6 2a e8 33 c4 0b f2 58 42 e9 84 bb 78 bd bf 97
+ c0 10 7d 55 bd b6 62 f5 c4 e0 fa b9 84 5c b5 14
+ 8e f7 39 2d d3 aa ff 93 ae 1e 6b 66 7b b3 d4 24
+ 76 16 d4 f5 ba 10 d4 cf d2 26 de 88 d3 9f 16 fb''',
+ 'e':'''01 00 01''',
+ 'd':'''53 33 9c fd b7 9f c8 46 6a 65 5c 73 16 ac a8 5c
+ 55 fd 8f 6d d8 98 fd af 11 95 17 ef 4f 52 e8 fd
+ 8e 25 8d f9 3f ee 18 0f a0 e4 ab 29 69 3c d8 3b
+ 15 2a 55 3d 4a c4 d1 81 2b 8b 9f a5 af 0e 7f 55
+ fe 73 04 df 41 57 09 26 f3 31 1f 15 c4 d6 5a 73
+ 2c 48 31 16 ee 3d 3d 2d 0a f3 54 9a d9 bf 7c bf
+ b7 8a d8 84 f8 4d 5b eb 04 72 4d c7 36 9b 31 de
+ f3 7d 0c f5 39 e9 cf cd d3 de 65 37 29 ea d5 d1 '''
+ }
+ ,
+ # Plaintext
+ '''66 28 19 4e 12 07 3d b0 3b a9 4c da 9e f9 53 23
+ 97 d5 0d ba 79 b9 87 00 4a fe fe 34''',
+ # Ciphertext
+ '''35 4f e6 7b 4a 12 6d 5d 35 fe 36 c7 77 79 1a 3f
+ 7b a1 3d ef 48 4e 2d 39 08 af f7 22 fa d4 68 fb
+ 21 69 6d e9 5d 0b e9 11 c2 d3 17 4f 8a fc c2 01
+ 03 5f 7b 6d 8e 69 40 2d e5 45 16 18 c2 1a 53 5f
+ a9 d7 bf c5 b8 dd 9f c2 43 f8 cf 92 7d b3 13 22
+ d6 e8 81 ea a9 1a 99 61 70 e6 57 a0 5a 26 64 26
+ d9 8c 88 00 3f 84 77 c1 22 70 94 a0 d9 fa 1e 8c
+ 40 24 30 9c e1 ec cc b5 21 00 35 d4 7a c7 2e 8a''',
+ # Random
+ '''18 b7 76 ea 21 06 9d 69 77 6a 33 e9 6b ad 48 e1
+ dd a0 a5 ef''',
+ SHA1
+ ),
+
+ #
+ # From in oaep-vect.txt to be found in Example 2.1
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''01 94 7c 7f ce 90 42 5f 47 27 9e 70 85 1f 25 d5
+ e6 23 16 fe 8a 1d f1 93 71 e3 e6 28 e2 60 54 3e
+ 49 01 ef 60 81 f6 8c 0b 81 41 19 0d 2a e8 da ba
+ 7d 12 50 ec 6d b6 36 e9 44 ec 37 22 87 7c 7c 1d
+ 0a 67 f1 4b 16 94 c5 f0 37 94 51 a4 3e 49 a3 2d
+ de 83 67 0b 73 da 91 a1 c9 9b c2 3b 43 6a 60 05
+ 5c 61 0f 0b af 99 c1 a0 79 56 5b 95 a3 f1 52 66
+ 32 d1 d4 da 60 f2 0e da 25 e6 53 c4 f0 02 76 6f
+ 45''',
+ 'e':'''01 00 01''',
+ 'd':'''08 23 f2 0f ad b5 da 89 08 8a 9d 00 89 3e 21 fa
+ 4a 1b 11 fb c9 3c 64 a3 be 0b aa ea 97 fb 3b 93
+ c3 ff 71 37 04 c1 9c 96 3c 1d 10 7a ae 99 05 47
+ 39 f7 9e 02 e1 86 de 86 f8 7a 6d de fe a6 d8 cc
+ d1 d3 c8 1a 47 bf a7 25 5b e2 06 01 a4 a4 b2 f0
+ 8a 16 7b 5e 27 9d 71 5b 1b 45 5b dd 7e ab 24 59
+ 41 d9 76 8b 9a ce fb 3c cd a5 95 2d a3 ce e7 25
+ 25 b4 50 16 63 a8 ee 15 c9 e9 92 d9 24 62 fe 39'''
+ },
+ # Plaintext
+ '''8f f0 0c aa 60 5c 70 28 30 63 4d 9a 6c 3d 42 c6
+ 52 b5 8c f1 d9 2f ec 57 0b ee e7''',
+ # Ciphertext
+ '''01 81 af 89 22 b9 fc b4 d7 9d 92 eb e1 98 15 99
+ 2f c0 c1 43 9d 8b cd 49 13 98 a0 f4 ad 3a 32 9a
+ 5b d9 38 55 60 db 53 26 83 c8 b7 da 04 e4 b1 2a
+ ed 6a ac df 47 1c 34 c9 cd a8 91 ad dc c2 df 34
+ 56 65 3a a6 38 2e 9a e5 9b 54 45 52 57 eb 09 9d
+ 56 2b be 10 45 3f 2b 6d 13 c5 9c 02 e1 0f 1f 8a
+ bb 5d a0 d0 57 09 32 da cf 2d 09 01 db 72 9d 0f
+ ef cc 05 4e 70 96 8e a5 40 c8 1b 04 bc ae fe 72
+ 0e''',
+ # Random
+ '''8c 40 7b 5e c2 89 9e 50 99 c5 3e 8c e7 93 bf 94
+ e7 1b 17 82''',
+ SHA1
+ ),
+
+ #
+ # From in oaep-vect.txt to be found in Example 10.1
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''ae 45 ed 56 01 ce c6 b8 cc 05 f8 03 93 5c 67 4d
+ db e0 d7 5c 4c 09 fd 79 51 fc 6b 0c ae c3 13 a8
+ df 39 97 0c 51 8b ff ba 5e d6 8f 3f 0d 7f 22 a4
+ 02 9d 41 3f 1a e0 7e 4e be 9e 41 77 ce 23 e7 f5
+ 40 4b 56 9e 4e e1 bd cf 3c 1f b0 3e f1 13 80 2d
+ 4f 85 5e b9 b5 13 4b 5a 7c 80 85 ad ca e6 fa 2f
+ a1 41 7e c3 76 3b e1 71 b0 c6 2b 76 0e de 23 c1
+ 2a d9 2b 98 08 84 c6 41 f5 a8 fa c2 6b da d4 a0
+ 33 81 a2 2f e1 b7 54 88 50 94 c8 25 06 d4 01 9a
+ 53 5a 28 6a fe b2 71 bb 9b a5 92 de 18 dc f6 00
+ c2 ae ea e5 6e 02 f7 cf 79 fc 14 cf 3b dc 7c d8
+ 4f eb bb f9 50 ca 90 30 4b 22 19 a7 aa 06 3a ef
+ a2 c3 c1 98 0e 56 0c d6 4a fe 77 95 85 b6 10 76
+ 57 b9 57 85 7e fd e6 01 09 88 ab 7d e4 17 fc 88
+ d8 f3 84 c4 e6 e7 2c 3f 94 3e 0c 31 c0 c4 a5 cc
+ 36 f8 79 d8 a3 ac 9d 7d 59 86 0e aa da 6b 83 bb''',
+ 'e':'''01 00 01''',
+ 'd':'''05 6b 04 21 6f e5 f3 54 ac 77 25 0a 4b 6b 0c 85
+ 25 a8 5c 59 b0 bd 80 c5 64 50 a2 2d 5f 43 8e 59
+ 6a 33 3a a8 75 e2 91 dd 43 f4 8c b8 8b 9d 5f c0
+ d4 99 f9 fc d1 c3 97 f9 af c0 70 cd 9e 39 8c 8d
+ 19 e6 1d b7 c7 41 0a 6b 26 75 df bf 5d 34 5b 80
+ 4d 20 1a dd 50 2d 5c e2 df cb 09 1c e9 99 7b be
+ be 57 30 6f 38 3e 4d 58 81 03 f0 36 f7 e8 5d 19
+ 34 d1 52 a3 23 e4 a8 db 45 1d 6f 4a 5b 1b 0f 10
+ 2c c1 50 e0 2f ee e2 b8 8d ea 4a d4 c1 ba cc b2
+ 4d 84 07 2d 14 e1 d2 4a 67 71 f7 40 8e e3 05 64
+ fb 86 d4 39 3a 34 bc f0 b7 88 50 1d 19 33 03 f1
+ 3a 22 84 b0 01 f0 f6 49 ea f7 93 28 d4 ac 5c 43
+ 0a b4 41 49 20 a9 46 0e d1 b7 bc 40 ec 65 3e 87
+ 6d 09 ab c5 09 ae 45 b5 25 19 01 16 a0 c2 61 01
+ 84 82 98 50 9c 1c 3b f3 a4 83 e7 27 40 54 e1 5e
+ 97 07 50 36 e9 89 f6 09 32 80 7b 52 57 75 1e 79'''
+ },
+ # Plaintext
+ '''8b ba 6b f8 2a 6c 0f 86 d5 f1 75 6e 97 95 68 70
+ b0 89 53 b0 6b 4e b2 05 bc 16 94 ee''',
+ # Ciphertext
+ '''53 ea 5d c0 8c d2 60 fb 3b 85 85 67 28 7f a9 15
+ 52 c3 0b 2f eb fb a2 13 f0 ae 87 70 2d 06 8d 19
+ ba b0 7f e5 74 52 3d fb 42 13 9d 68 c3 c5 af ee
+ e0 bf e4 cb 79 69 cb f3 82 b8 04 d6 e6 13 96 14
+ 4e 2d 0e 60 74 1f 89 93 c3 01 4b 58 b9 b1 95 7a
+ 8b ab cd 23 af 85 4f 4c 35 6f b1 66 2a a7 2b fc
+ c7 e5 86 55 9d c4 28 0d 16 0c 12 67 85 a7 23 eb
+ ee be ff 71 f1 15 94 44 0a ae f8 7d 10 79 3a 87
+ 74 a2 39 d4 a0 4c 87 fe 14 67 b9 da f8 52 08 ec
+ 6c 72 55 79 4a 96 cc 29 14 2f 9a 8b d4 18 e3 c1
+ fd 67 34 4b 0c d0 82 9d f3 b2 be c6 02 53 19 62
+ 93 c6 b3 4d 3f 75 d3 2f 21 3d d4 5c 62 73 d5 05
+ ad f4 cc ed 10 57 cb 75 8f c2 6a ee fa 44 12 55
+ ed 4e 64 c1 99 ee 07 5e 7f 16 64 61 82 fd b4 64
+ 73 9b 68 ab 5d af f0 e6 3e 95 52 01 68 24 f0 54
+ bf 4d 3c 8c 90 a9 7b b6 b6 55 32 84 eb 42 9f cc''',
+ # Random
+ '''47 e1 ab 71 19 fe e5 6c 95 ee 5e aa d8 6f 40 d0
+ aa 63 bd 33''',
+ SHA1
+ ),
+ )
+
+ def testEncrypt1(self):
+ # Verify encryption using all test vectors
+ for test in self._testData:
+ # Build the key
+ comps = [ long(rws(test[0][x]),16) for x in ('n','e') ]
+ key = RSA.construct(comps)
+ # RNG that takes its random numbers from a pool given
+ # at initialization
+ class randGen:
+ def __init__(self, data):
+ self.data = data
+ self.idx = 0
+ def __call__(self, N):
+ r = self.data[self.idx:N]
+ self.idx += N
+ return r
+ # The real test
+ key._randfunc = randGen(t2b(test[3]))
+ cipher = PKCS.new(key, test[4])
+ ct = cipher.encrypt(t2b(test[1]))
+ self.assertEqual(ct, t2b(test[2]))
+
+ def testEncrypt2(self):
+ # Verify that encryption fails if plaintext is too long
+ pt = '\x00'*(128-2*20-2+1)
+ cipher = PKCS.new(self.key1024)
+ self.assertRaises(ValueError, cipher.encrypt, pt)
+
+ def testDecrypt1(self):
+ # Verify decryption using all test vectors
+ for test in self._testData:
+ # Build the key
+ comps = [ long(rws(test[0][x]),16) for x in ('n','e','d') ]
+ key = RSA.construct(comps)
+ # The real test
+ cipher = PKCS.new(key, test[4])
+ pt = cipher.decrypt(t2b(test[2]))
+ self.assertEqual(pt, t2b(test[1]))
+
+ def testDecrypt2(self):
+ # Simplest possible negative tests
+ for ct_size in (127,128,129):
+ cipher = PKCS.new(self.key1024)
+ self.assertRaises(ValueError, cipher.decrypt, bchr(0x00)*ct_size)
+
+ def testEncryptDecrypt1(self):
+ # Encrypt/Decrypt messages of length [0..128-2*20-2]
+ for pt_len in xrange(0,128-2*20-2):
+ pt = self.rng(pt_len)
+ ct = PKCS.encrypt(pt, self.key1024)
+ pt2 = PKCS.decrypt(ct, self.key1024)
+ self.assertEqual(pt,pt2)
+
+ def testEncryptDecrypt1(self):
+ # Helper function to monitor what's requested from RNG
+ global asked
+ def localRng(N):
+ global asked
+ asked += N
+ return self.rng(N)
+ # Verify that OAEP is friendly to all hashes
+ for hashmod in (MD2,MD5,SHA1,SHA256,RIPEMD):
+ # Verify that encrypt() asks for as many random bytes
+ # as the hash output size
+ asked = 0
+ pt = self.rng(40)
+ self.key1024._randfunc = localRng
+ cipher = PKCS.new(self.key1024, hashmod)
+ ct = cipher.encrypt(pt)
+ self.assertEqual(cipher.decrypt(ct), pt)
+ self.failUnless(asked > hashmod.digest_size)
+
+ def testEncryptDecrypt2(self):
+ # Verify that OAEP supports labels
+ pt = self.rng(35)
+ xlabel = self.rng(22)
+ cipher = PKCS.new(self.key1024, label=xlabel)
+ ct = cipher.encrypt(pt)
+ self.assertEqual(cipher.decrypt(ct), pt)
+
+ def testEncryptDecrypt3(self):
+ # Verify that encrypt() uses the custom MGF
+ global mgfcalls
+ # Helper function to monitor what's requested from MGF
+ def newMGF(seed,maskLen):
+ global mgfcalls
+ mgfcalls += 1
+ return bchr(0x00)*maskLen
+ mgfcalls = 0
+ pt = self.rng(32)
+ cipher = PKCS.new(self.key1024, mgfunc=newMGF)
+ ct = cipher.encrypt(pt)
+ self.assertEqual(mgfcalls, 2)
+ self.assertEqual(cipher.decrypt(ct), pt)
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(PKCS1_OAEP_Tests)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Hash/__init__.py
new file mode 100644
index 000000000..bb19f9b3f
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/__init__.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/__init__.py: Self-test for hash modules
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for hash modules"""
+
+__revision__ = "$Id$"
+
+def get_tests(config={}):
+ tests = []
+ from Crypto.SelfTest.Hash import test_HMAC; tests += test_HMAC.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_MD2; tests += test_MD2.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_MD4; tests += test_MD4.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_MD5; tests += test_MD5.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_RIPEMD; tests += test_RIPEMD.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_SHA; tests += test_SHA.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_SHA256; tests += test_SHA256.get_tests(config=config)
+ try:
+ from Crypto.SelfTest.Hash import test_SHA224; tests += test_SHA224.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_SHA384; tests += test_SHA384.get_tests(config=config)
+ from Crypto.SelfTest.Hash import test_SHA512; tests += test_SHA512.get_tests(config=config)
+ except ImportError:
+ import sys
+ sys.stderr.write("SelfTest: warning: not testing SHA224/SHA384/SHA512 modules (not available)\n")
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/common.py b/lib/Python/Lib/Crypto/SelfTest/Hash/common.py
new file mode 100644
index 000000000..f77fb0f1d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/common.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/common.py: Common code for Crypto.SelfTest.Hash
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-testing for PyCrypto hash modules"""
+
+__revision__ = "$Id$"
+
+import sys
+import unittest
+import binascii
+from Crypto.Util.py3compat import *
+
+# For compatibility with Python 2.1 and Python 2.2
+if sys.hexversion < 0x02030000:
+ # Python 2.1 doesn't have a dict() function
+ # Python 2.2 dict() function raises TypeError if you do dict(MD5='blah')
+ def dict(**kwargs):
+ return kwargs.copy()
+else:
+ dict = dict
+
+
+class HashDigestSizeSelfTest(unittest.TestCase):
+
+ def __init__(self, hashmod, description, expected):
+ unittest.TestCase.__init__(self)
+ self.hashmod = hashmod
+ self.expected = expected
+ self.description = description
+
+ def shortDescription(self):
+ return self.description
+
+ def runTest(self):
+ self.failUnless(hasattr(self.hashmod, "digest_size"))
+ self.assertEquals(self.hashmod.digest_size, self.expected)
+ h = self.hashmod.new()
+ self.failUnless(hasattr(h, "digest_size"))
+ self.assertEquals(h.digest_size, self.expected)
+
+
+class HashSelfTest(unittest.TestCase):
+
+ def __init__(self, hashmod, description, expected, input):
+ unittest.TestCase.__init__(self)
+ self.hashmod = hashmod
+ self.expected = expected
+ self.input = input
+ self.description = description
+
+ def shortDescription(self):
+ return self.description
+
+ def runTest(self):
+ h = self.hashmod.new()
+ h.update(self.input)
+
+ out1 = binascii.b2a_hex(h.digest())
+ out2 = h.hexdigest()
+
+ h = self.hashmod.new(self.input)
+
+ out3 = h.hexdigest()
+ out4 = binascii.b2a_hex(h.digest())
+
+ # PY3K: hexdigest() should return str(), and digest() bytes
+ self.assertEqual(self.expected, out1) # h = .new(); h.update(data); h.digest()
+ if sys.version_info[0] == 2:
+ self.assertEqual(self.expected, out2) # h = .new(); h.update(data); h.hexdigest()
+ self.assertEqual(self.expected, out3) # h = .new(data); h.hexdigest()
+ else:
+ self.assertEqual(self.expected.decode(), out2) # h = .new(); h.update(data); h.hexdigest()
+ self.assertEqual(self.expected.decode(), out3) # h = .new(data); h.hexdigest()
+ self.assertEqual(self.expected, out4) # h = .new(data); h.digest()
+
+ # Verify that new() object method produces a fresh hash object
+ h2 = h.new()
+ h2.update(self.input)
+ out5 = binascii.b2a_hex(h2.digest())
+ self.assertEqual(self.expected, out5)
+
+class HashTestOID(unittest.TestCase):
+ def __init__(self, hashmod, oid):
+ unittest.TestCase.__init__(self)
+ self.hashmod = hashmod
+ self.oid = oid
+
+ def runTest(self):
+ h = self.hashmod.new()
+ if self.oid==None:
+ try:
+ raised = 0
+ a = h.oid
+ except AttributeError:
+ raised = 1
+ self.assertEqual(raised,1)
+ else:
+ self.assertEqual(h.oid, self.oid)
+
+class MACSelfTest(unittest.TestCase):
+
+ def __init__(self, hashmod, description, expected_dict, input, key, hashmods):
+ unittest.TestCase.__init__(self)
+ self.hashmod = hashmod
+ self.expected_dict = expected_dict
+ self.input = input
+ self.key = key
+ self.hashmods = hashmods
+ self.description = description
+
+ def shortDescription(self):
+ return self.description
+
+ def runTest(self):
+ for hashname in self.expected_dict.keys():
+ hashmod = self.hashmods[hashname]
+ key = binascii.a2b_hex(b(self.key))
+ data = binascii.a2b_hex(b(self.input))
+
+ # Strip whitespace from the expected string (which should be in lowercase-hex)
+ expected = b("".join(self.expected_dict[hashname].split()))
+
+ h = self.hashmod.new(key, digestmod=hashmod)
+ h.update(data)
+ out1 = binascii.b2a_hex(h.digest())
+ out2 = h.hexdigest()
+
+ h = self.hashmod.new(key, data, hashmod)
+
+ out3 = h.hexdigest()
+ out4 = binascii.b2a_hex(h.digest())
+
+ # Test .copy()
+ h2 = h.copy()
+ h.update(b("blah blah blah")) # Corrupt the original hash object
+ out5 = binascii.b2a_hex(h2.digest()) # The copied hash object should return the correct result
+
+ # PY3K: hexdigest() should return str(), and digest() bytes
+ self.assertEqual(expected, out1)
+ if sys.version_info[0] == 2:
+ self.assertEqual(expected, out2)
+ self.assertEqual(expected, out3)
+ else:
+ self.assertEqual(expected.decode(), out2)
+ self.assertEqual(expected.decode(), out3)
+ self.assertEqual(expected, out4)
+ self.assertEqual(expected, out5)
+
+def make_hash_tests(module, module_name, test_data, digest_size, oid=None):
+ tests = []
+ for i in range(len(test_data)):
+ row = test_data[i]
+ (expected, input) = map(b,row[0:2])
+ if len(row) < 3:
+ description = repr(input)
+ else:
+ description = row[2].encode('latin-1')
+ name = "%s #%d: %s" % (module_name, i+1, description)
+ tests.append(HashSelfTest(module, name, expected, input))
+ if oid is not None:
+ oid = b(oid)
+ name = "%s #%d: digest_size" % (module_name, i+1)
+ tests.append(HashDigestSizeSelfTest(module, name, digest_size))
+ tests.append(HashTestOID(module, oid))
+ return tests
+
+def make_mac_tests(module, module_name, test_data, hashmods):
+ tests = []
+ for i in range(len(test_data)):
+ row = test_data[i]
+ (key, data, results, description) = row
+ name = "%s #%d: %s" % (module_name, i+1, description)
+ tests.append(MACSelfTest(module, name, results, data, key, hashmods))
+ return tests
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_HMAC.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_HMAC.py
new file mode 100644
index 000000000..c01c97b69
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_HMAC.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/HMAC.py: Self-test for the HMAC module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.HMAC"""
+
+__revision__ = "$Id$"
+
+from common import dict # For compatibility with Python 2.1 and 2.2
+from Crypto.Util.py3compat import *
+
+# This is a list of (key, data, results, description) tuples.
+test_data = [
+ ## Test vectors from RFC 2202 ##
+ # Test that the default hashmod is MD5
+ ('0b' * 16,
+ '4869205468657265',
+ dict(default='9294727a3638bb1c13f48ef8158bfc9d'),
+ 'default-is-MD5'),
+
+ # Test case 1 (MD5)
+ ('0b' * 16,
+ '4869205468657265',
+ dict(MD5='9294727a3638bb1c13f48ef8158bfc9d'),
+ 'RFC 2202 #1-MD5 (HMAC-MD5)'),
+
+ # Test case 1 (SHA1)
+ ('0b' * 20,
+ '4869205468657265',
+ dict(SHA1='b617318655057264e28bc0b6fb378c8ef146be00'),
+ 'RFC 2202 #1-SHA1 (HMAC-SHA1)'),
+
+ # Test case 2
+ ('4a656665',
+ '7768617420646f2079612077616e7420666f72206e6f7468696e673f',
+ dict(MD5='750c783e6ab0b503eaa86e310a5db738',
+ SHA1='effcdf6ae5eb2fa2d27416d5f184df9c259a7c79'),
+ 'RFC 2202 #2 (HMAC-MD5/SHA1)'),
+
+ # Test case 3 (MD5)
+ ('aa' * 16,
+ 'dd' * 50,
+ dict(MD5='56be34521d144c88dbb8c733f0e8b3f6'),
+ 'RFC 2202 #3-MD5 (HMAC-MD5)'),
+
+ # Test case 3 (SHA1)
+ ('aa' * 20,
+ 'dd' * 50,
+ dict(SHA1='125d7342b9ac11cd91a39af48aa17b4f63f175d3'),
+ 'RFC 2202 #3-SHA1 (HMAC-SHA1)'),
+
+ # Test case 4
+ ('0102030405060708090a0b0c0d0e0f10111213141516171819',
+ 'cd' * 50,
+ dict(MD5='697eaf0aca3a3aea3a75164746ffaa79',
+ SHA1='4c9007f4026250c6bc8414f9bf50c86c2d7235da'),
+ 'RFC 2202 #4 (HMAC-MD5/SHA1)'),
+
+ # Test case 5 (MD5)
+ ('0c' * 16,
+ '546573742057697468205472756e636174696f6e',
+ dict(MD5='56461ef2342edc00f9bab995690efd4c'),
+ 'RFC 2202 #5-MD5 (HMAC-MD5)'),
+
+ # Test case 5 (SHA1)
+ # NB: We do not implement hash truncation, so we only test the full hash here.
+ ('0c' * 20,
+ '546573742057697468205472756e636174696f6e',
+ dict(SHA1='4c1a03424b55e07fe7f27be1d58bb9324a9a5a04'),
+ 'RFC 2202 #5-SHA1 (HMAC-SHA1)'),
+
+ # Test case 6
+ ('aa' * 80,
+ '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a'
+ + '65204b6579202d2048617368204b6579204669727374',
+ dict(MD5='6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd',
+ SHA1='aa4ae5e15272d00e95705637ce8a3b55ed402112'),
+ 'RFC 2202 #6 (HMAC-MD5/SHA1)'),
+
+ # Test case 7
+ ('aa' * 80,
+ '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a'
+ + '65204b657920616e64204c6172676572205468616e204f6e6520426c6f636b2d'
+ + '53697a652044617461',
+ dict(MD5='6f630fad67cda0ee1fb1f562db3aa53e',
+ SHA1='e8e99d0f45237d786d6bbaa7965c7808bbff1a91'),
+ 'RFC 2202 #7 (HMAC-MD5/SHA1)'),
+
+ ## Test vectors from RFC 4231 ##
+ # 4.2. Test Case 1
+ ('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b',
+ '4869205468657265',
+ dict(SHA256='''
+ b0344c61d8db38535ca8afceaf0bf12b
+ 881dc200c9833da726e9376c2e32cff7
+ '''),
+ 'RFC 4231 #1 (HMAC-SHA256)'),
+
+ # 4.3. Test Case 2 - Test with a key shorter than the length of the HMAC
+ # output.
+ ('4a656665',
+ '7768617420646f2079612077616e7420666f72206e6f7468696e673f',
+ dict(SHA256='''
+ 5bdcc146bf60754e6a042426089575c7
+ 5a003f089d2739839dec58b964ec3843
+ '''),
+ 'RFC 4231 #2 (HMAC-SHA256)'),
+
+ # 4.4. Test Case 3 - Test with a combined length of key and data that is
+ # larger than 64 bytes (= block-size of SHA-224 and SHA-256).
+ ('aa' * 20,
+ 'dd' * 50,
+ dict(SHA256='''
+ 773ea91e36800e46854db8ebd09181a7
+ 2959098b3ef8c122d9635514ced565fe
+ '''),
+ 'RFC 4231 #3 (HMAC-SHA256)'),
+
+ # 4.5. Test Case 4 - Test with a combined length of key and data that is
+ # larger than 64 bytes (= block-size of SHA-224 and SHA-256).
+ ('0102030405060708090a0b0c0d0e0f10111213141516171819',
+ 'cd' * 50,
+ dict(SHA256='''
+ 82558a389a443c0ea4cc819899f2083a
+ 85f0faa3e578f8077a2e3ff46729665b
+ '''),
+ 'RFC 4231 #4 (HMAC-SHA256)'),
+
+ # 4.6. Test Case 5 - Test with a truncation of output to 128 bits.
+ #
+ # Not included because we do not implement hash truncation.
+ #
+
+ # 4.7. Test Case 6 - Test with a key larger than 128 bytes (= block-size of
+ # SHA-384 and SHA-512).
+ ('aa' * 131,
+ '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a'
+ + '65204b6579202d2048617368204b6579204669727374',
+ dict(SHA256='''
+ 60e431591ee0b67f0d8a26aacbf5b77f
+ 8e0bc6213728c5140546040f0ee37f54
+ '''),
+ 'RFC 4231 #6 (HMAC-SHA256)'),
+
+ # 4.8. Test Case 7 - Test with a key and data that is larger than 128 bytes
+ # (= block-size of SHA-384 and SHA-512).
+ ('aa' * 131,
+ '5468697320697320612074657374207573696e672061206c6172676572207468'
+ + '616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074'
+ + '68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565'
+ + '647320746f20626520686173686564206265666f7265206265696e6720757365'
+ + '642062792074686520484d414320616c676f726974686d2e',
+ dict(SHA256='''
+ 9b09ffa71b942fcb27635fbcd5b0e944
+ bfdc63644f0713938a7f51535c3a35e2
+ '''),
+ 'RFC 4231 #7 (HMAC-SHA256)'),
+]
+
+hashlib_test_data = [
+ # Test case 8 (SHA224)
+ ('4a656665',
+ '7768617420646f2079612077616e74'
+ + '20666f72206e6f7468696e673f',
+ dict(SHA224='a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44'),
+ 'RFC 4634 8.4 SHA224 (HMAC-SHA224)'),
+
+ # Test case 9 (SHA384)
+ ('4a656665',
+ '7768617420646f2079612077616e74'
+ + '20666f72206e6f7468696e673f',
+ dict(SHA384='af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649'),
+ 'RFC 4634 8.4 SHA384 (HMAC-SHA384)'),
+
+ # Test case 10 (SHA512)
+ ('4a656665',
+ '7768617420646f2079612077616e74'
+ + '20666f72206e6f7468696e673f',
+ dict(SHA512='164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737'),
+ 'RFC 4634 8.4 SHA512 (HMAC-SHA512)'),
+
+]
+
+def get_tests(config={}):
+ global test_data
+ from Crypto.Hash import HMAC, MD5, SHA as SHA1, SHA256
+ from common import make_mac_tests
+ hashmods = dict(MD5=MD5, SHA1=SHA1, SHA256=SHA256, default=None)
+ try:
+ from Crypto.Hash import SHA224, SHA384, SHA512
+ hashmods.update(dict(SHA224=SHA224, SHA384=SHA384, SHA512=SHA512))
+ test_data += hashlib_test_data
+ except ImportError:
+ import sys
+ sys.stderr.write("SelfTest: warning: not testing HMAC-SHA224/384/512 (not available)\n")
+ return make_mac_tests(HMAC, "HMAC", test_data, hashmods)
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD2.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD2.py
new file mode 100644
index 000000000..db636d4c5
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD2.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/MD2.py: Self-test for the MD2 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.MD2"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+ # Test vectors from RFC 1319
+ ('8350e5a3e24c153df2275c9f80692773', '', "'' (empty string)"),
+ ('32ec01ec4a6dac72c0ab96fb34c0b5d1', 'a'),
+ ('da853b0d3f88d99b30283a69e6ded6bb', 'abc'),
+ ('ab4f496bfb2a530b219ff33031fe06b0', 'message digest'),
+
+ ('4e8ddff3650292ab5a4108c3aa47940b', 'abcdefghijklmnopqrstuvwxyz',
+ 'a-z'),
+
+ ('da33def2a42df13975352846c30338cd',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
+ 'A-Z, a-z, 0-9'),
+
+ ('d5976f79d83d3a0dc9806c3c66f3efd8',
+ '1234567890123456789012345678901234567890123456'
+ + '7890123456789012345678901234567890',
+ "'1234567890' * 8"),
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import MD2
+ from common import make_hash_tests
+ return make_hash_tests(MD2, "MD2", test_data,
+ digest_size=16,
+ oid="\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02")
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD4.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD4.py
new file mode 100644
index 000000000..1727bb65b
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD4.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/MD4.py: Self-test for the MD4 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.MD4"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+ # Test vectors from RFC 1320
+ ('31d6cfe0d16ae931b73c59d7e0c089c0', '', "'' (empty string)"),
+ ('bde52cb31de33e46245e05fbdbd6fb24', 'a'),
+ ('a448017aaf21d8525fc10ae87aa6729d', 'abc'),
+ ('d9130a8164549fe818874806e1c7014b', 'message digest'),
+
+ ('d79e1c308aa5bbcdeea8ed63df412da9', 'abcdefghijklmnopqrstuvwxyz',
+ 'a-z'),
+
+ ('043f8582f241db351ce627e153e7f0e4',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
+ 'A-Z, a-z, 0-9'),
+
+ ('e33b4ddc9c38f2199c3e7b164fcc0536',
+ '1234567890123456789012345678901234567890123456'
+ + '7890123456789012345678901234567890',
+ "'1234567890' * 8"),
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import MD4
+ from common import make_hash_tests
+ return make_hash_tests(MD4, "MD4", test_data,
+ digest_size=16,
+ oid="\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x04")
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD5.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD5.py
new file mode 100644
index 000000000..2e293fc83
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_MD5.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/MD5.py: Self-test for the MD5 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.MD5"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+ # Test vectors from RFC 1321
+ ('d41d8cd98f00b204e9800998ecf8427e', '', "'' (empty string)"),
+ ('0cc175b9c0f1b6a831c399e269772661', 'a'),
+ ('900150983cd24fb0d6963f7d28e17f72', 'abc'),
+ ('f96b697d7cb7938d525a2f31aaf161d0', 'message digest'),
+
+ ('c3fcd3d76192e4007dfb496cca67e13b', 'abcdefghijklmnopqrstuvwxyz',
+ 'a-z'),
+
+ ('d174ab98d277d9f5a5611c2c9f419d9f',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
+ 'A-Z, a-z, 0-9'),
+
+ ('57edf4a22be3c955ac49da2e2107b67a',
+ '1234567890123456789012345678901234567890123456'
+ + '7890123456789012345678901234567890',
+ "'1234567890' * 8"),
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import MD5
+ from common import make_hash_tests
+ return make_hash_tests(MD5, "MD5", test_data,
+ digest_size=16,
+ oid="\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05")
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_RIPEMD.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_RIPEMD.py
new file mode 100644
index 000000000..6673a9343
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_RIPEMD.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/test_RIPEMD.py: Self-test for the RIPEMD-160 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+#"""Self-test suite for Crypto.Hash.RIPEMD"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+ # Test vectors downloaded 2008-09-12 from
+ # http://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+ ('9c1185a5c5e9fc54612808977ee8f548b2258d31', '', "'' (empty string)"),
+ ('0bdc9d2d256b3ee9daae347be6f4dc835a467ffe', 'a'),
+ ('8eb208f7e05d987a9b044a8e98c6b087f15a0bfc', 'abc'),
+ ('5d0689ef49d2fae572b881b123a85ffa21595f36', 'message digest'),
+
+ ('f71c27109c692c1b56bbdceb5b9d2865b3708dbc',
+ 'abcdefghijklmnopqrstuvwxyz',
+ 'a-z'),
+
+ ('12a053384a9c0c88e405a06c27dcf49ada62eb2b',
+ 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq',
+ 'abcdbcd...pnopq'),
+
+ ('b0e20b6e3116640286ed3a87a5713079b21f5189',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
+ 'A-Z, a-z, 0-9'),
+
+ ('9b752e45573d4b39f4dbd3323cab82bf63326bfb',
+ '1234567890' * 8,
+ "'1234567890' * 8"),
+
+ ('52783243c1697bdbe16d37f97f68f08325dc1528',
+ 'a' * 10**6,
+ '"a" * 10**6'),
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import RIPEMD
+ from common import make_hash_tests
+ return make_hash_tests(RIPEMD, "RIPEMD", test_data,
+ digest_size=20,
+ oid="\x06\x05\x2b\x24\x03\02\x01")
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA.py
new file mode 100644
index 000000000..7d72e7724
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/SHA.py: Self-test for the SHA-1 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.SHA"""
+
+__revision__ = "$Id$"
+
+from Crypto.Util.py3compat import *
+
+# Test vectors from various sources
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+ # FIPS PUB 180-2, A.1 - "One-Block Message"
+ ('a9993e364706816aba3e25717850c26c9cd0d89d', 'abc'),
+
+ # FIPS PUB 180-2, A.2 - "Multi-Block Message"
+ ('84983e441c3bd26ebaae4aa1f95129e5e54670f1',
+ 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'),
+
+ # FIPS PUB 180-2, A.3 - "Long Message"
+# ('34aa973cd4c4daa4f61eeb2bdbad27316534016f',
+# 'a' * 10**6,
+# '"a" * 10**6'),
+
+ # RFC 3174: Section 7.3, "TEST4" (multiple of 512 bits)
+ ('dea356a2cddd90c7a7ecedc5ebb563934f460452',
+ '01234567' * 80,
+ '"01234567" * 80'),
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import SHA
+ from common import make_hash_tests
+ return make_hash_tests(SHA, "SHA", test_data,
+ digest_size=20,
+ oid="\x06\x05\x2B\x0E\x03\x02\x1A")
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA224.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA224.py
new file mode 100644
index 000000000..a60f35a9d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA224.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/test_SHA224.py: Self-test for the SHA-224 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.SHA224"""
+
+__revision__ = "$Id$"
+
+# Test vectors from various sources
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+
+ # RFC 3874: Section 3.1, "Test Vector #1
+ ('23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7', 'abc'),
+
+ # RFC 3874: Section 3.2, "Test Vector #2
+ ('75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525', 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'),
+
+ # RFC 3874: Section 3.3, "Test Vector #3
+ ('20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67', 'a' * 10**6, "'a' * 10**6"),
+
+ # Examples from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm
+ ('d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', ''),
+
+ ('49b08defa65e644cbf8a2dd9270bdededabc741997d1dadd42026d7b',
+ 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'),
+
+ ('58911e7fccf2971a7d07f93162d8bd13568e71aa8fc86fc1fe9043d1',
+ 'Frank jagt im komplett verwahrlosten Taxi quer durch Bayern'),
+
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import SHA224
+ from common import make_hash_tests
+ return make_hash_tests(SHA224, "SHA224", test_data,
+ digest_size=28,
+ oid='\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04')
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA256.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA256.py
new file mode 100644
index 000000000..4b451100d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA256.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/test_SHA256.py: Self-test for the SHA-256 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.SHA256"""
+
+__revision__ = "$Id$"
+
+import unittest
+from Crypto.Util.py3compat import *
+
+class LargeSHA256Test(unittest.TestCase):
+ def runTest(self):
+ """SHA256: 512/520 MiB test"""
+ from Crypto.Hash import SHA256
+ zeros = bchr(0x00) * (1024*1024)
+
+ h = SHA256.new(zeros)
+ for i in xrange(511):
+ h.update(zeros)
+
+ # This test vector is from PyCrypto's old testdata.py file.
+ self.assertEqual('9acca8e8c22201155389f65abbf6bc9723edc7384ead80503839f49dcc56d767', h.hexdigest()) # 512 MiB
+
+ for i in xrange(8):
+ h.update(zeros)
+
+ # This test vector is from PyCrypto's old testdata.py file.
+ self.assertEqual('abf51ad954b246009dfe5a50ecd582fd5b8f1b8b27f30393853c3ef721e7fa6e', h.hexdigest()) # 520 MiB
+
+def get_tests(config={}):
+ # Test vectors from FIPS PUB 180-2
+ # This is a list of (expected_result, input[, description]) tuples.
+ test_data = [
+ # FIPS PUB 180-2, B.1 - "One-Block Message"
+ ('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
+ 'abc'),
+
+ # FIPS PUB 180-2, B.2 - "Multi-Block Message"
+ ('248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1',
+ 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'),
+
+ # FIPS PUB 180-2, B.3 - "Long Message"
+ ('cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0',
+ 'a' * 10**6,
+ '"a" * 10**6'),
+
+ # Test for an old PyCrypto bug.
+ ('f7fd017a3c721ce7ff03f3552c0813adcc48b7f33f07e5e2ba71e23ea393d103',
+ 'This message is precisely 55 bytes long, to test a bug.',
+ 'Length = 55 (mod 64)'),
+
+ # Example from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm
+ ('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', ''),
+
+ ('d32b568cd1b96d459e7291ebf4b25d007f275c9f13149beeb782fac0716613f8',
+ 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'),
+ ]
+
+ from Crypto.Hash import SHA256
+ from common import make_hash_tests
+ tests = make_hash_tests(SHA256, "SHA256", test_data,
+ digest_size=32,
+ oid="\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01")
+
+ if config.get('slow_tests'):
+ tests += [LargeSHA256Test()]
+
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA384.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA384.py
new file mode 100644
index 000000000..b7a72c057
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA384.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/test_SHA.py: Self-test for the SHA-384 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.SHA384"""
+
+__revision__ = "$Id$"
+
+# Test vectors from various sources
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+
+ # RFC 4634: Section Page 8.4, "Test 1"
+ ('cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7', 'abc'),
+
+ # RFC 4634: Section Page 8.4, "Test 2.2"
+ ('09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039', 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'),
+
+ # RFC 4634: Section Page 8.4, "Test 3"
+ ('9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985', 'a' * 10**6, "'a' * 10**6"),
+
+ # Taken from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm
+ ('38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', ''),
+
+ # Example from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm
+ ('71e8383a4cea32d6fd6877495db2ee353542f46fa44bc23100bca48f3366b84e809f0708e81041f427c6d5219a286677',
+ 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'),
+
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import SHA384
+ from common import make_hash_tests
+ return make_hash_tests(SHA384, "SHA384", test_data,
+ digest_size=48,
+ oid='\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02')
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA512.py b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA512.py
new file mode 100644
index 000000000..cb86177ee
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Hash/test_SHA512.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Hash/test_SHA512.py: Self-test for the SHA-512 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Hash.SHA512"""
+
+__revision__ = "$Id$"
+
+# Test vectors from various sources
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+
+ # RFC 4634: Section Page 8.4, "Test 1"
+ ('ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f', 'abc'),
+
+ # RFC 4634: Section Page 8.4, "Test 2.1"
+ ('8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909', 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'),
+
+ # RFC 4634: Section Page 8.4, "Test 3"
+ ('e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b', 'a' * 10**6, "'a' * 10**6"),
+
+ # Taken from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm
+ ('cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', ''),
+
+ ('af9ed2de700433b803240a552b41b5a472a6ef3fe1431a722b2063c75e9f07451f67a28e37d09cde769424c96aea6f8971389db9e1993d6c565c3c71b855723c', 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'),
+]
+
+def get_tests(config={}):
+ from Crypto.Hash import SHA512
+ from common import make_hash_tests
+ return make_hash_tests(SHA512, "SHA512", test_data,
+ digest_size=64,
+ oid="\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03")
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Protocol/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Protocol/__init__.py
new file mode 100644
index 000000000..a62c670f3
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Protocol/__init__.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Protocol/__init__.py: Self-tests for Crypto.Protocol
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for Crypto.Protocol"""
+
+__revision__ = "$Id$"
+
+def get_tests(config={}):
+ tests = []
+ from Crypto.SelfTest.Protocol import test_chaffing; tests += test_chaffing.get_tests(config=config)
+ from Crypto.SelfTest.Protocol import test_rfc1751; tests += test_rfc1751.get_tests(config=config)
+ from Crypto.SelfTest.Protocol import test_AllOrNothing; tests += test_AllOrNothing.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py
new file mode 100644
index 000000000..a211eab7e
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py
@@ -0,0 +1,76 @@
+#
+# Test script for Crypto.Protocol.AllOrNothing
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew Kuchling and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import unittest
+from Crypto.Protocol import AllOrNothing
+from Crypto.Util.py3compat import *
+
+text = b("""\
+When in the Course of human events, it becomes necessary for one people to
+dissolve the political bands which have connected them with another, and to
+assume among the powers of the earth, the separate and equal station to which
+the Laws of Nature and of Nature's God entitle them, a decent respect to the
+opinions of mankind requires that they should declare the causes which impel
+them to the separation.
+
+We hold these truths to be self-evident, that all men are created equal, that
+they are endowed by their Creator with certain unalienable Rights, that among
+these are Life, Liberty, and the pursuit of Happiness. That to secure these
+rights, Governments are instituted among Men, deriving their just powers from
+the consent of the governed. That whenever any Form of Government becomes
+destructive of these ends, it is the Right of the People to alter or to
+abolish it, and to institute new Government, laying its foundation on such
+principles and organizing its powers in such form, as to them shall seem most
+likely to effect their Safety and Happiness.
+""")
+
+class AllOrNothingTest (unittest.TestCase):
+
+ def runTest(self):
+ "Simple test of AllOrNothing"
+
+ from Crypto.Cipher import AES
+ import base64
+
+ # The current AllOrNothing will fail
+ # every so often. Repeat the test
+ # several times to force this.
+ for i in range(50):
+ x = AllOrNothing.AllOrNothing(AES)
+
+ msgblocks = x.digest(text)
+
+ # get a new undigest-only object so there's no leakage
+ y = AllOrNothing.AllOrNothing(AES)
+ text2 = y.undigest(msgblocks)
+ self.assertEqual(text, text2)
+
+def get_tests(config={}):
+ return [AllOrNothingTest()]
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/lib/Python/Lib/Crypto/SelfTest/Protocol/test_KDF.py b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_KDF.py
new file mode 100644
index 000000000..119836bb7
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_KDF.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Protocol/test_KDF.py: Self-test for key derivation functions
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import unittest
+from binascii import unhexlify
+
+from Crypto.SelfTest.st_common import list_test_cases
+from Crypto.Hash import SHA as SHA1,HMAC
+
+from Crypto.Protocol.KDF import *
+
+def t2b(t): return unhexlify(b(t))
+
+class PBKDF1_Tests(unittest.TestCase):
+
+ # List of tuples with test data.
+ # Each tuple is made up by:
+ # Item #0: a pass phrase
+ # Item #1: salt (8 bytes encoded in hex)
+ # Item #2: output key length
+ # Item #3: iterations to use
+ # Item #4: expected result (encoded in hex)
+ _testData = (
+ # From http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf
+ ("password","78578E5A5D63CB06",16,1000,"DC19847E05C64D2FAF10EBFB4A3D2A20"),
+ )
+
+ def test1(self):
+ v = self._testData[0]
+ res = PBKDF1(v[0], t2b(v[1]), v[2], v[3], SHA1)
+ self.assertEqual(res, t2b(v[4]))
+
+class PBKDF2_Tests(unittest.TestCase):
+
+ # List of tuples with test data.
+ # Each tuple is made up by:
+ # Item #0: a pass phrase
+ # Item #1: salt (encoded in hex)
+ # Item #2: output key length
+ # Item #3: iterations to use
+ # Item #4: expected result (encoded in hex)
+ _testData = (
+ # From http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf
+ ("password","78578E5A5D63CB06",24,2048,"BFDE6BE94DF7E11DD409BCE20A0255EC327CB936FFE93643"),
+ # From RFC 6050
+ ("password","73616c74", 20, 1, "0c60c80f961f0e71f3a9b524af6012062fe037a6"),
+ ("password","73616c74", 20, 2, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
+ ("password","73616c74", 20, 4096, "4b007901b765489abead49d926f721d065a429c1"),
+ ("passwordPASSWORDpassword","73616c7453414c5473616c7453414c5473616c7453414c5473616c7453414c5473616c74",
+ 25, 4096, "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"),
+ ( 'pass\x00word',"7361006c74",16,4096, "56fa6aa75548099dcc37d7f03425e0c3"),
+ )
+
+ def test1(self):
+ # Test only for HMAC-SHA1 as PRF
+
+ def prf(p,s):
+ return HMAC.new(p,s,SHA1).digest()
+
+ for i in xrange(len(self._testData)):
+ v = self._testData[i]
+ res = PBKDF2(v[0], t2b(v[1]), v[2], v[3])
+ res2 = PBKDF2(v[0], t2b(v[1]), v[2], v[3], prf)
+ self.assertEqual(res, t2b(v[4]))
+ self.assertEqual(res, res2)
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(PBKDF1_Tests)
+ tests += list_test_cases(PBKDF2_Tests)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4
diff --git a/lib/Python/Lib/Crypto/SelfTest/Protocol/test_chaffing.py b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_chaffing.py
new file mode 100644
index 000000000..5fa012099
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_chaffing.py
@@ -0,0 +1,74 @@
+#
+# Test script for Crypto.Protocol.Chaffing
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew Kuchling and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import unittest
+from Crypto.Protocol import Chaffing
+
+text = """\
+When in the Course of human events, it becomes necessary for one people to
+dissolve the political bands which have connected them with another, and to
+assume among the powers of the earth, the separate and equal station to which
+the Laws of Nature and of Nature's God entitle them, a decent respect to the
+opinions of mankind requires that they should declare the causes which impel
+them to the separation.
+
+We hold these truths to be self-evident, that all men are created equal, that
+they are endowed by their Creator with certain unalienable Rights, that among
+these are Life, Liberty, and the pursuit of Happiness. That to secure these
+rights, Governments are instituted among Men, deriving their just powers from
+the consent of the governed. That whenever any Form of Government becomes
+destructive of these ends, it is the Right of the People to alter or to
+abolish it, and to institute new Government, laying its foundation on such
+principles and organizing its powers in such form, as to them shall seem most
+likely to effect their Safety and Happiness.
+"""
+
+class ChaffingTest (unittest.TestCase):
+
+ def runTest(self):
+ "Simple tests of chaffing and winnowing"
+ # Test constructors
+ Chaffing.Chaff()
+ Chaffing.Chaff(0.5, 1)
+ self.assertRaises(ValueError, Chaffing.Chaff, factor=-1)
+ self.assertRaises(ValueError, Chaffing.Chaff, blocksper=-1)
+
+ data = [(1, 'data1', 'data1'), (2, 'data2', 'data2')]
+ c = Chaffing.Chaff(1.0, 1)
+ c.chaff(data)
+ chaff = c.chaff(data)
+ self.assertEqual(len(chaff), 4)
+
+ c = Chaffing.Chaff(0.0, 1)
+ chaff = c.chaff(data)
+ self.assertEqual(len(chaff), 2)
+
+def get_tests(config={}):
+ return [ChaffingTest()]
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/lib/Python/Lib/Crypto/SelfTest/Protocol/test_rfc1751.py b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_rfc1751.py
new file mode 100644
index 000000000..0878cc510
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Protocol/test_rfc1751.py
@@ -0,0 +1,62 @@
+#
+# Test script for Crypto.Util.RFC1751.
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew Kuchling and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import binascii
+import unittest
+from Crypto.Util import RFC1751
+from Crypto.Util.py3compat import *
+
+test_data = [('EB33F77EE73D4053', 'TIDE ITCH SLOW REIN RULE MOT'),
+ ('CCAC2AED591056BE4F90FD441C534766',
+ 'RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE'),
+ ('EFF81F9BFBC65350920CDD7416DE8009',
+ 'TROD MUTE TAIL WARM CHAR KONG HAAG CITY BORE O TEAL AWL')
+ ]
+
+class RFC1751Test_k2e (unittest.TestCase):
+
+ def runTest (self):
+ "Check converting keys to English"
+ for key, words in test_data:
+ key=binascii.a2b_hex(b(key))
+ self.assertEqual(RFC1751.key_to_english(key), words)
+
+class RFC1751Test_e2k (unittest.TestCase):
+
+ def runTest (self):
+ "Check converting English strings to keys"
+ for key, words in test_data:
+ key=binascii.a2b_hex(b(key))
+ self.assertEqual(RFC1751.english_to_key(words), key)
+
+# class RFC1751Test
+
+def get_tests(config={}):
+ return [RFC1751Test_k2e(), RFC1751Test_e2k()]
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/lib/Python/Lib/Crypto/SelfTest/PublicKey/__init__.py b/lib/Python/Lib/Crypto/SelfTest/PublicKey/__init__.py
new file mode 100644
index 000000000..61ba53f29
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/PublicKey/__init__.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/PublicKey/__init__.py: Self-test for public key crypto
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for public-key crypto"""
+
+__revision__ = "$Id$"
+
+import os
+
+def get_tests(config={}):
+ tests = []
+ from Crypto.SelfTest.PublicKey import test_DSA; tests += test_DSA.get_tests(config=config)
+ from Crypto.SelfTest.PublicKey import test_RSA; tests += test_RSA.get_tests(config=config)
+ from Crypto.SelfTest.PublicKey import test_importKey; tests += test_importKey.get_tests(config=config)
+ from Crypto.SelfTest.PublicKey import test_ElGamal; tests += test_ElGamal.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_DSA.py b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_DSA.py
new file mode 100644
index 000000000..b05f69acc
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_DSA.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/PublicKey/test_DSA.py: Self-test for the DSA primitive
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.PublicKey.DSA"""
+
+__revision__ = "$Id$"
+
+import sys
+import os
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+import unittest
+from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex
+
+def _sws(s):
+ """Remove whitespace from a text or byte string"""
+ if isinstance(s,str):
+ return "".join(s.split())
+ else:
+ return b("").join(s.split())
+
+class DSATest(unittest.TestCase):
+ # Test vector from "Appendix 5. Example of the DSA" of
+ # "Digital Signature Standard (DSS)",
+ # U.S. Department of Commerce/National Institute of Standards and Technology
+ # FIPS 186-2 (+Change Notice), 2000 January 27.
+ # http://csrc.nist.gov/publications/fips/fips186-2/fips186-2-change1.pdf
+
+ y = _sws("""19131871 d75b1612 a819f29d 78d1b0d7 346f7aa7 7bb62a85
+ 9bfd6c56 75da9d21 2d3a36ef 1672ef66 0b8c7c25 5cc0ec74
+ 858fba33 f44c0669 9630a76b 030ee333""")
+
+ g = _sws("""626d0278 39ea0a13 413163a5 5b4cb500 299d5522 956cefcb
+ 3bff10f3 99ce2c2e 71cb9de5 fa24babf 58e5b795 21925c9c
+ c42e9f6f 464b088c c572af53 e6d78802""")
+
+ p = _sws("""8df2a494 492276aa 3d25759b b06869cb eac0d83a fb8d0cf7
+ cbb8324f 0d7882e5 d0762fc5 b7210eaf c2e9adac 32ab7aac
+ 49693dfb f83724c2 ec0736ee 31c80291""")
+
+ q = _sws("""c773218c 737ec8ee 993b4f2d ed30f48e dace915f""")
+
+ x = _sws("""2070b322 3dba372f de1c0ffc 7b2e3b49 8b260614""")
+
+ k = _sws("""358dad57 1462710f 50e254cf 1a376b2b deaadfbf""")
+ k_inverse = _sws("""0d516729 8202e49b 4116ac10 4fc3f415 ae52f917""")
+ m = b2a_hex(b("abc"))
+ m_hash = _sws("""a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d""")
+ r = _sws("""8bac1ab6 6410435c b7181f95 b16ab97c 92b341c0""")
+ s = _sws("""41e2345f 1f56df24 58f426d1 55b4ba2d b6dcd8c8""")
+
+ def setUp(self):
+ global DSA, Random, bytes_to_long, size
+ from Crypto.PublicKey import DSA
+ from Crypto import Random
+ from Crypto.Util.number import bytes_to_long, inverse, size
+
+ self.dsa = DSA
+
+ def test_generate_1arg(self):
+ """DSA (default implementation) generated key (1 argument)"""
+ dsaObj = self.dsa.generate(1024)
+ self._check_private_key(dsaObj)
+ pub = dsaObj.publickey()
+ self._check_public_key(pub)
+
+ def test_generate_2arg(self):
+ """DSA (default implementation) generated key (2 arguments)"""
+ dsaObj = self.dsa.generate(1024, Random.new().read)
+ self._check_private_key(dsaObj)
+ pub = dsaObj.publickey()
+ self._check_public_key(pub)
+
+ def test_construct_4tuple(self):
+ """DSA (default implementation) constructed key (4-tuple)"""
+ (y, g, p, q) = [bytes_to_long(a2b_hex(param)) for param in (self.y, self.g, self.p, self.q)]
+ dsaObj = self.dsa.construct((y, g, p, q))
+ self._test_verification(dsaObj)
+
+ def test_construct_5tuple(self):
+ """DSA (default implementation) constructed key (5-tuple)"""
+ (y, g, p, q, x) = [bytes_to_long(a2b_hex(param)) for param in (self.y, self.g, self.p, self.q, self.x)]
+ dsaObj = self.dsa.construct((y, g, p, q, x))
+ self._test_signing(dsaObj)
+ self._test_verification(dsaObj)
+
+ def _check_private_key(self, dsaObj):
+ # Check capabilities
+ self.assertEqual(1, dsaObj.has_private())
+ self.assertEqual(1, dsaObj.can_sign())
+ self.assertEqual(0, dsaObj.can_encrypt())
+ self.assertEqual(0, dsaObj.can_blind())
+
+ # Check dsaObj.[ygpqx] -> dsaObj.key.[ygpqx] mapping
+ self.assertEqual(dsaObj.y, dsaObj.key.y)
+ self.assertEqual(dsaObj.g, dsaObj.key.g)
+ self.assertEqual(dsaObj.p, dsaObj.key.p)
+ self.assertEqual(dsaObj.q, dsaObj.key.q)
+ self.assertEqual(dsaObj.x, dsaObj.key.x)
+
+ # Sanity check key data
+ self.assertEqual(1, dsaObj.p > dsaObj.q) # p > q
+ self.assertEqual(160, size(dsaObj.q)) # size(q) == 160 bits
+ self.assertEqual(0, (dsaObj.p - 1) % dsaObj.q) # q is a divisor of p-1
+ self.assertEqual(dsaObj.y, pow(dsaObj.g, dsaObj.x, dsaObj.p)) # y == g**x mod p
+ self.assertEqual(1, 0 < dsaObj.x < dsaObj.q) # 0 < x < q
+
+ def _check_public_key(self, dsaObj):
+ k = a2b_hex(self.k)
+ m_hash = a2b_hex(self.m_hash)
+
+ # Check capabilities
+ self.assertEqual(0, dsaObj.has_private())
+ self.assertEqual(1, dsaObj.can_sign())
+ self.assertEqual(0, dsaObj.can_encrypt())
+ self.assertEqual(0, dsaObj.can_blind())
+
+ # Check dsaObj.[ygpq] -> dsaObj.key.[ygpq] mapping
+ self.assertEqual(dsaObj.y, dsaObj.key.y)
+ self.assertEqual(dsaObj.g, dsaObj.key.g)
+ self.assertEqual(dsaObj.p, dsaObj.key.p)
+ self.assertEqual(dsaObj.q, dsaObj.key.q)
+
+ # Check that private parameters are all missing
+ self.assertEqual(0, hasattr(dsaObj, 'x'))
+ self.assertEqual(0, hasattr(dsaObj.key, 'x'))
+
+ # Sanity check key data
+ self.assertEqual(1, dsaObj.p > dsaObj.q) # p > q
+ self.assertEqual(160, size(dsaObj.q)) # size(q) == 160 bits
+ self.assertEqual(0, (dsaObj.p - 1) % dsaObj.q) # q is a divisor of p-1
+
+ # Public-only key objects should raise an error when .sign() is called
+ self.assertRaises(TypeError, dsaObj.sign, m_hash, k)
+
+ # Check __eq__ and __ne__
+ self.assertEqual(dsaObj.publickey() == dsaObj.publickey(),True) # assert_
+ self.assertEqual(dsaObj.publickey() != dsaObj.publickey(),False) # failIf
+
+ def _test_signing(self, dsaObj):
+ k = a2b_hex(self.k)
+ m_hash = a2b_hex(self.m_hash)
+ r = bytes_to_long(a2b_hex(self.r))
+ s = bytes_to_long(a2b_hex(self.s))
+ (r_out, s_out) = dsaObj.sign(m_hash, k)
+ self.assertEqual((r, s), (r_out, s_out))
+
+ def _test_verification(self, dsaObj):
+ m_hash = a2b_hex(self.m_hash)
+ r = bytes_to_long(a2b_hex(self.r))
+ s = bytes_to_long(a2b_hex(self.s))
+ self.assertEqual(1, dsaObj.verify(m_hash, (r, s)))
+ self.assertEqual(0, dsaObj.verify(m_hash + b("\0"), (r, s)))
+
+class DSAFastMathTest(DSATest):
+ def setUp(self):
+ DSATest.setUp(self)
+ self.dsa = DSA.DSAImplementation(use_fast_math=True)
+
+ def test_generate_1arg(self):
+ """DSA (_fastmath implementation) generated key (1 argument)"""
+ DSATest.test_generate_1arg(self)
+
+ def test_generate_2arg(self):
+ """DSA (_fastmath implementation) generated key (2 arguments)"""
+ DSATest.test_generate_2arg(self)
+
+ def test_construct_4tuple(self):
+ """DSA (_fastmath implementation) constructed key (4-tuple)"""
+ DSATest.test_construct_4tuple(self)
+
+ def test_construct_5tuple(self):
+ """DSA (_fastmath implementation) constructed key (5-tuple)"""
+ DSATest.test_construct_5tuple(self)
+
+class DSASlowMathTest(DSATest):
+ def setUp(self):
+ DSATest.setUp(self)
+ self.dsa = DSA.DSAImplementation(use_fast_math=False)
+
+ def test_generate_1arg(self):
+ """DSA (_slowmath implementation) generated key (1 argument)"""
+ DSATest.test_generate_1arg(self)
+
+ def test_generate_2arg(self):
+ """DSA (_slowmath implementation) generated key (2 arguments)"""
+ DSATest.test_generate_2arg(self)
+
+ def test_construct_4tuple(self):
+ """DSA (_slowmath implementation) constructed key (4-tuple)"""
+ DSATest.test_construct_4tuple(self)
+
+ def test_construct_5tuple(self):
+ """DSA (_slowmath implementation) constructed key (5-tuple)"""
+ DSATest.test_construct_5tuple(self)
+
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(DSATest)
+ try:
+ from Crypto.PublicKey import _fastmath
+ tests += list_test_cases(DSAFastMathTest)
+ except ImportError:
+ from distutils.sysconfig import get_config_var
+ import inspect
+ _fm_path = os.path.normpath(os.path.dirname(os.path.abspath(
+ inspect.getfile(inspect.currentframe())))
+ +"/../../PublicKey/_fastmath"+get_config_var("SO"))
+ if os.path.exists(_fm_path):
+ raise ImportError("While the _fastmath module exists, importing "+
+ "it failed. This may point to the gmp or mpir shared library "+
+ "not being in the path. _fastmath was found at "+_fm_path)
+ tests += list_test_cases(DSASlowMathTest)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_ElGamal.py b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_ElGamal.py
new file mode 100644
index 000000000..cdee8cf67
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_ElGamal.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/PublicKey/test_ElGamal.py: Self-test for the ElGamal primitive
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.PublicKey.ElGamal"""
+
+__revision__ = "$Id$"
+
+import unittest
+from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex
+from Crypto import Random
+from Crypto.PublicKey import ElGamal
+from Crypto.Util.number import *
+from Crypto.Util.py3compat import *
+
+class ElGamalTest(unittest.TestCase):
+
+ #
+ # Test vectors
+ #
+ # There seem to be no real ElGamal test vectors available in the
+ # public domain. The following test vectors have been generated
+ # with libgcrypt 1.5.0.
+ #
+ # Encryption
+ tve=[
+ {
+ # 256 bits
+ 'p' :'BA4CAEAAED8CBE952AFD2126C63EB3B345D65C2A0A73D2A3AD4138B6D09BD933',
+ 'g' :'05',
+ 'y' :'60D063600ECED7C7C55146020E7A31C4476E9793BEAED420FEC9E77604CAE4EF',
+ 'x' :'1D391BA2EE3C37FE1BA175A69B2C73A11238AD77675932',
+ 'k' :'F5893C5BAB4131264066F57AB3D8AD89E391A0B68A68A1',
+ 'pt' :'48656C6C6F207468657265',
+ 'ct1':'32BFD5F487966CEA9E9356715788C491EC515E4ED48B58F0F00971E93AAA5EC7',
+ 'ct2':'7BE8FBFF317C93E82FCEF9BD515284BA506603FEA25D01C0CB874A31F315EE68'
+ },
+
+ {
+ # 512 bits
+ 'p' :'F1B18AE9F7B4E08FDA9A04832F4E919D89462FD31BF12F92791A93519F75076D6CE3942689CDFF2F344CAFF0F82D01864F69F3AECF566C774CBACF728B81A227',
+ 'g' :'07',
+ 'y' :'688628C676E4F05D630E1BE39D0066178CA7AA83836B645DE5ADD359B4825A12B02EF4252E4E6FA9BEC1DB0BE90F6D7C8629CABB6E531F472B2664868156E20C',
+ 'x' :'14E60B1BDFD33436C0DA8A22FDC14A2CCDBBED0627CE68',
+ 'k' :'38DBF14E1F319BDA9BAB33EEEADCAF6B2EA5250577ACE7',
+ 'pt' :'48656C6C6F207468657265',
+ 'ct1':'290F8530C2CC312EC46178724F196F308AD4C523CEABB001FACB0506BFED676083FE0F27AC688B5C749AB3CB8A80CD6F7094DBA421FB19442F5A413E06A9772B',
+ 'ct2':'1D69AAAD1DC50493FB1B8E8721D621D683F3BF1321BE21BC4A43E11B40C9D4D9C80DE3AAC2AB60D31782B16B61112E68220889D53C4C3136EE6F6CE61F8A23A0'
+ }
+ ]
+
+ # Signature
+ tvs=[
+ {
+ # 256 bits
+ 'p' :'D2F3C41EA66530838A704A48FFAC9334F4701ECE3A97CEE4C69DD01AE7129DD7',
+ 'g' :'05',
+ 'y' :'C3F9417DC0DAFEA6A05C1D2333B7A95E63B3F4F28CC962254B3256984D1012E7',
+ 'x' :'165E4A39BE44D5A2D8B1332D416BC559616F536BC735BB',
+ 'k' :'C7F0C794A7EAD726E25A47FF8928013680E73C51DD3D7D99BFDA8F492585928F',
+ 'h' :'48656C6C6F207468657265',
+ 'sig1':'35CA98133779E2073EF31165AFCDEB764DD54E96ADE851715495F9C635E1E7C2',
+ 'sig2':'0135B88B1151279FE5D8078D4FC685EE81177EE9802AB123A73925FC1CB059A7',
+ },
+ {
+ # 512 bits
+ 'p' :'E24CF3A4B8A6AF749DCA6D714282FE4AABEEE44A53BB6ED15FBE32B5D3C3EF9CC4124A2ECA331F3C1C1B667ACA3766825217E7B5F9856648D95F05330C6A19CF',
+ 'g' :'0B',
+ 'y' :'2AD3A1049CA5D4ED207B2431C79A8719BB4073D4A94E450EA6CEE8A760EB07ADB67C0D52C275EE85D7B52789061EE45F2F37D9B2AE522A51C28329766BFE68AC',
+ 'x' :'16CBB4F46D9ECCF24FF9F7E63CAA3BD8936341555062AB',
+ 'k' :'8A3D89A4E429FD2476D7D717251FB79BF900FFE77444E6BB8299DC3F84D0DD57ABAB50732AE158EA52F5B9E7D8813E81FD9F79470AE22F8F1CF9AEC820A78C69',
+ 'h' :'48656C6C6F207468657265',
+ 'sig1':'BE001AABAFFF976EC9016198FBFEA14CBEF96B000CCC0063D3324016F9E91FE80D8F9325812ED24DDB2B4D4CF4430B169880B3CE88313B53255BD4EC0378586F',
+ 'sig2':'5E266F3F837BA204E3BBB6DBECC0611429D96F8C7CE8F4EFDF9D4CB681C2A954468A357BF4242CEC7418B51DFC081BCD21299EF5B5A0DDEF3A139A1817503DDE',
+ }
+ ]
+
+ def test_generate_128(self):
+ self._test_random_key(128)
+
+ def test_generate_512(self):
+ self._test_random_key(512)
+
+ def test_encryption(self):
+ for tv in self.tve:
+ for as_longs in (0,1):
+ d = self.convert_tv(tv, as_longs)
+ key = ElGamal.construct(d['key'])
+ ct = key.encrypt(d['pt'], d['k'])
+ self.assertEquals(ct[0], d['ct1'])
+ self.assertEquals(ct[1], d['ct2'])
+
+ def test_decryption(self):
+ for tv in self.tve:
+ for as_longs in (0,1):
+ d = self.convert_tv(tv, as_longs)
+ key = ElGamal.construct(d['key'])
+ pt = key.decrypt((d['ct1'], d['ct2']))
+ self.assertEquals(pt, d['pt'])
+
+ def test_signing(self):
+ for tv in self.tvs:
+ for as_longs in (0,1):
+ d = self.convert_tv(tv, as_longs)
+ key = ElGamal.construct(d['key'])
+ sig1, sig2 = key.sign(d['h'], d['k'])
+ self.assertEquals(sig1, d['sig1'])
+ self.assertEquals(sig2, d['sig2'])
+
+ def test_verification(self):
+ for tv in self.tvs:
+ for as_longs in (0,1):
+ d = self.convert_tv(tv, as_longs)
+ key = ElGamal.construct(d['key'])
+ # Positive test
+ res = key.verify( d['h'], (d['sig1'],d['sig2']) )
+ self.failUnless(res)
+ # Negative test
+ res = key.verify( d['h'], (d['sig1']+1,d['sig2']) )
+ self.failIf(res)
+
+ def convert_tv(self, tv, as_longs=0):
+ """Convert a test vector from textual form (hexadecimal ascii
+ to either integers or byte strings."""
+ key_comps = 'p','g','y','x'
+ tv2 = {}
+ for c in tv.keys():
+ tv2[c] = a2b_hex(tv[c])
+ if as_longs or c in key_comps or c in ('sig1','sig2'):
+ tv2[c] = bytes_to_long(tv2[c])
+ tv2['key']=[]
+ for c in key_comps:
+ tv2['key'] += [tv2[c]]
+ del tv2[c]
+ return tv2
+
+ def _test_random_key(self, bits):
+ elgObj = ElGamal.generate(bits, Random.new().read)
+ self._check_private_key(elgObj)
+ self._exercise_primitive(elgObj)
+ pub = elgObj.publickey()
+ self._check_public_key(pub)
+ self._exercise_public_primitive(elgObj)
+
+ def _check_private_key(self, elgObj):
+
+ # Check capabilities
+ self.failUnless(elgObj.has_private())
+ self.failUnless(elgObj.can_sign())
+ self.failUnless(elgObj.can_encrypt())
+
+ # Sanity check key data
+ self.failUnless(1<elgObj.g<(elgObj.p-1))
+ self.assertEquals(pow(elgObj.g, elgObj.p-1, elgObj.p), 1)
+ self.failUnless(1<elgObj.x<(elgObj.p-1))
+ self.assertEquals(pow(elgObj.g, elgObj.x, elgObj.p), elgObj.y)
+
+ def _check_public_key(self, elgObj):
+
+ # Check capabilities
+ self.failIf(elgObj.has_private())
+ self.failUnless(elgObj.can_sign())
+ self.failUnless(elgObj.can_encrypt())
+
+ # Sanity check key data
+ self.failUnless(1<elgObj.g<(elgObj.p-1))
+ self.assertEquals(pow(elgObj.g, elgObj.p-1, elgObj.p), 1)
+
+ def _exercise_primitive(self, elgObj):
+ # Test encryption/decryption
+ plaintext = b("Test")
+ ciphertext = elgObj.encrypt(plaintext, 123456789L)
+ plaintextP = elgObj.decrypt(ciphertext)
+ self.assertEquals(plaintext, plaintextP)
+
+ # Test signature/verification
+ signature = elgObj.sign(plaintext, 987654321L)
+ elgObj.verify(plaintext, signature)
+
+ def _exercise_public_primitive(self, elgObj):
+ plaintext = b("Test")
+ ciphertext = elgObj.encrypt(plaintext, 123456789L)
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(ElGamalTest)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
diff --git a/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_RSA.py b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_RSA.py
new file mode 100644
index 000000000..c971042b2
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_RSA.py
@@ -0,0 +1,415 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/PublicKey/test_RSA.py: Self-test for the RSA primitive
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.PublicKey.RSA"""
+
+__revision__ = "$Id$"
+
+import sys
+import os
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+import unittest
+from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex
+
+class RSATest(unittest.TestCase):
+ # Test vectors from "RSA-OAEP and RSA-PSS test vectors (.zip file)"
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ # See RSADSI's PKCS#1 page at
+ # http://www.rsa.com/rsalabs/node.asp?id=2125
+
+ # from oaep-int.txt
+
+ # TODO: PyCrypto treats the message as starting *after* the leading "00"
+ # TODO: That behaviour should probably be changed in the future.
+ plaintext = """
+ eb 7a 19 ac e9 e3 00 63 50 e3 29 50 4b 45 e2
+ ca 82 31 0b 26 dc d8 7d 5c 68 f1 ee a8 f5 52 67
+ c3 1b 2e 8b b4 25 1f 84 d7 e0 b2 c0 46 26 f5 af
+ f9 3e dc fb 25 c9 c2 b3 ff 8a e1 0e 83 9a 2d db
+ 4c dc fe 4f f4 77 28 b4 a1 b7 c1 36 2b aa d2 9a
+ b4 8d 28 69 d5 02 41 21 43 58 11 59 1b e3 92 f9
+ 82 fb 3e 87 d0 95 ae b4 04 48 db 97 2f 3a c1 4f
+ 7b c2 75 19 52 81 ce 32 d2 f1 b7 6d 4d 35 3e 2d
+ """
+
+ ciphertext = """
+ 12 53 e0 4d c0 a5 39 7b b4 4a 7a b8 7e 9b f2 a0
+ 39 a3 3d 1e 99 6f c8 2a 94 cc d3 00 74 c9 5d f7
+ 63 72 20 17 06 9e 52 68 da 5d 1c 0b 4f 87 2c f6
+ 53 c1 1d f8 23 14 a6 79 68 df ea e2 8d ef 04 bb
+ 6d 84 b1 c3 1d 65 4a 19 70 e5 78 3b d6 eb 96 a0
+ 24 c2 ca 2f 4a 90 fe 9f 2e f5 c9 c1 40 e5 bb 48
+ da 95 36 ad 87 00 c8 4f c9 13 0a de a7 4e 55 8d
+ 51 a7 4d df 85 d8 b5 0d e9 68 38 d6 06 3e 09 55
+ """
+
+ modulus = """
+ bb f8 2f 09 06 82 ce 9c 23 38 ac 2b 9d a8 71 f7
+ 36 8d 07 ee d4 10 43 a4 40 d6 b6 f0 74 54 f5 1f
+ b8 df ba af 03 5c 02 ab 61 ea 48 ce eb 6f cd 48
+ 76 ed 52 0d 60 e1 ec 46 19 71 9d 8a 5b 8b 80 7f
+ af b8 e0 a3 df c7 37 72 3e e6 b4 b7 d9 3a 25 84
+ ee 6a 64 9d 06 09 53 74 88 34 b2 45 45 98 39 4e
+ e0 aa b1 2d 7b 61 a5 1f 52 7a 9a 41 f6 c1 68 7f
+ e2 53 72 98 ca 2a 8f 59 46 f8 e5 fd 09 1d bd cb
+ """
+
+ e = 0x11L # public exponent
+
+ prime_factor = """
+ c9 7f b1 f0 27 f4 53 f6 34 12 33 ea aa d1 d9 35
+ 3f 6c 42 d0 88 66 b1 d0 5a 0f 20 35 02 8b 9d 86
+ 98 40 b4 16 66 b4 2e 92 ea 0d a3 b4 32 04 b5 cf
+ ce 33 52 52 4d 04 16 a5 a4 41 e7 00 af 46 15 03
+ """
+
+ def setUp(self):
+ global RSA, Random, bytes_to_long
+ from Crypto.PublicKey import RSA
+ from Crypto import Random
+ from Crypto.Util.number import bytes_to_long, inverse
+ self.n = bytes_to_long(a2b_hex(self.modulus))
+ self.p = bytes_to_long(a2b_hex(self.prime_factor))
+
+ # Compute q, d, and u from n, e, and p
+ self.q = divmod(self.n, self.p)[0]
+ self.d = inverse(self.e, (self.p-1)*(self.q-1))
+ self.u = inverse(self.p, self.q) # u = e**-1 (mod q)
+
+ self.rsa = RSA
+
+ def test_generate_1arg(self):
+ """RSA (default implementation) generated key (1 argument)"""
+ rsaObj = self.rsa.generate(1024)
+ self._check_private_key(rsaObj)
+ self._exercise_primitive(rsaObj)
+ pub = rsaObj.publickey()
+ self._check_public_key(pub)
+ self._exercise_public_primitive(rsaObj)
+
+ def test_generate_2arg(self):
+ """RSA (default implementation) generated key (2 arguments)"""
+ rsaObj = self.rsa.generate(1024, Random.new().read)
+ self._check_private_key(rsaObj)
+ self._exercise_primitive(rsaObj)
+ pub = rsaObj.publickey()
+ self._check_public_key(pub)
+ self._exercise_public_primitive(rsaObj)
+
+ def test_generate_3args(self):
+ rsaObj = self.rsa.generate(1024, Random.new().read,e=65537)
+ self._check_private_key(rsaObj)
+ self._exercise_primitive(rsaObj)
+ pub = rsaObj.publickey()
+ self._check_public_key(pub)
+ self._exercise_public_primitive(rsaObj)
+ self.assertEqual(65537,rsaObj.e)
+
+ def test_construct_2tuple(self):
+ """RSA (default implementation) constructed key (2-tuple)"""
+ pub = self.rsa.construct((self.n, self.e))
+ self._check_public_key(pub)
+ self._check_encryption(pub)
+ self._check_verification(pub)
+
+ def test_construct_3tuple(self):
+ """RSA (default implementation) constructed key (3-tuple)"""
+ rsaObj = self.rsa.construct((self.n, self.e, self.d))
+ self._check_encryption(rsaObj)
+ self._check_decryption(rsaObj)
+ self._check_signing(rsaObj)
+ self._check_verification(rsaObj)
+
+ def test_construct_4tuple(self):
+ """RSA (default implementation) constructed key (4-tuple)"""
+ rsaObj = self.rsa.construct((self.n, self.e, self.d, self.p))
+ self._check_encryption(rsaObj)
+ self._check_decryption(rsaObj)
+ self._check_signing(rsaObj)
+ self._check_verification(rsaObj)
+
+ def test_construct_5tuple(self):
+ """RSA (default implementation) constructed key (5-tuple)"""
+ rsaObj = self.rsa.construct((self.n, self.e, self.d, self.p, self.q))
+ self._check_private_key(rsaObj)
+ self._check_encryption(rsaObj)
+ self._check_decryption(rsaObj)
+ self._check_signing(rsaObj)
+ self._check_verification(rsaObj)
+
+ def test_construct_6tuple(self):
+ """RSA (default implementation) constructed key (6-tuple)"""
+ rsaObj = self.rsa.construct((self.n, self.e, self.d, self.p, self.q, self.u))
+ self._check_private_key(rsaObj)
+ self._check_encryption(rsaObj)
+ self._check_decryption(rsaObj)
+ self._check_signing(rsaObj)
+ self._check_verification(rsaObj)
+
+ def test_factoring(self):
+ rsaObj = self.rsa.construct([self.n, self.e, self.d])
+ self.failUnless(rsaObj.p==self.p or rsaObj.p==self.q)
+ self.failUnless(rsaObj.q==self.p or rsaObj.q==self.q)
+ self.failUnless(rsaObj.q*rsaObj.p == self.n)
+
+ self.assertRaises(ValueError, self.rsa.construct, [self.n, self.e, self.n-1])
+
+ def _check_private_key(self, rsaObj):
+ # Check capabilities
+ self.assertEqual(1, rsaObj.has_private())
+ self.assertEqual(1, rsaObj.can_sign())
+ self.assertEqual(1, rsaObj.can_encrypt())
+ self.assertEqual(1, rsaObj.can_blind())
+
+ # Check rsaObj.[nedpqu] -> rsaObj.key.[nedpqu] mapping
+ self.assertEqual(rsaObj.n, rsaObj.key.n)
+ self.assertEqual(rsaObj.e, rsaObj.key.e)
+ self.assertEqual(rsaObj.d, rsaObj.key.d)
+ self.assertEqual(rsaObj.p, rsaObj.key.p)
+ self.assertEqual(rsaObj.q, rsaObj.key.q)
+ self.assertEqual(rsaObj.u, rsaObj.key.u)
+
+ # Sanity check key data
+ self.assertEqual(rsaObj.n, rsaObj.p * rsaObj.q) # n = pq
+ self.assertEqual(1, rsaObj.d * rsaObj.e % ((rsaObj.p-1) * (rsaObj.q-1))) # ed = 1 (mod (p-1)(q-1))
+ self.assertEqual(1, rsaObj.p * rsaObj.u % rsaObj.q) # pu = 1 (mod q)
+ self.assertEqual(1, rsaObj.p > 1) # p > 1
+ self.assertEqual(1, rsaObj.q > 1) # q > 1
+ self.assertEqual(1, rsaObj.e > 1) # e > 1
+ self.assertEqual(1, rsaObj.d > 1) # d > 1
+
+ def _check_public_key(self, rsaObj):
+ ciphertext = a2b_hex(self.ciphertext)
+
+ # Check capabilities
+ self.assertEqual(0, rsaObj.has_private())
+ self.assertEqual(1, rsaObj.can_sign())
+ self.assertEqual(1, rsaObj.can_encrypt())
+ self.assertEqual(1, rsaObj.can_blind())
+
+ # Check rsaObj.[ne] -> rsaObj.key.[ne] mapping
+ self.assertEqual(rsaObj.n, rsaObj.key.n)
+ self.assertEqual(rsaObj.e, rsaObj.key.e)
+
+ # Check that private parameters are all missing
+ self.assertEqual(0, hasattr(rsaObj, 'd'))
+ self.assertEqual(0, hasattr(rsaObj, 'p'))
+ self.assertEqual(0, hasattr(rsaObj, 'q'))
+ self.assertEqual(0, hasattr(rsaObj, 'u'))
+ self.assertEqual(0, hasattr(rsaObj.key, 'd'))
+ self.assertEqual(0, hasattr(rsaObj.key, 'p'))
+ self.assertEqual(0, hasattr(rsaObj.key, 'q'))
+ self.assertEqual(0, hasattr(rsaObj.key, 'u'))
+
+ # Sanity check key data
+ self.assertEqual(1, rsaObj.e > 1) # e > 1
+
+ # Public keys should not be able to sign or decrypt
+ self.assertRaises(TypeError, rsaObj.sign, ciphertext, b(""))
+ self.assertRaises(TypeError, rsaObj.decrypt, ciphertext)
+
+ # Check __eq__ and __ne__
+ self.assertEqual(rsaObj.publickey() == rsaObj.publickey(),True) # assert_
+ self.assertEqual(rsaObj.publickey() != rsaObj.publickey(),False) # failIf
+
+ def _exercise_primitive(self, rsaObj):
+ # Since we're using a randomly-generated key, we can't check the test
+ # vector, but we can make sure encryption and decryption are inverse
+ # operations.
+ ciphertext = a2b_hex(self.ciphertext)
+
+ # Test decryption
+ plaintext = rsaObj.decrypt((ciphertext,))
+
+ # Test encryption (2 arguments)
+ (new_ciphertext2,) = rsaObj.encrypt(plaintext, b(""))
+ self.assertEqual(b2a_hex(ciphertext), b2a_hex(new_ciphertext2))
+
+ # Test blinded decryption
+ blinding_factor = Random.new().read(len(ciphertext)-1)
+ blinded_ctext = rsaObj.blind(ciphertext, blinding_factor)
+ blinded_ptext = rsaObj.decrypt((blinded_ctext,))
+ unblinded_plaintext = rsaObj.unblind(blinded_ptext, blinding_factor)
+ self.assertEqual(b2a_hex(plaintext), b2a_hex(unblinded_plaintext))
+
+ # Test signing (2 arguments)
+ signature2 = rsaObj.sign(ciphertext, b(""))
+ self.assertEqual((bytes_to_long(plaintext),), signature2)
+
+ # Test verification
+ self.assertEqual(1, rsaObj.verify(ciphertext, (bytes_to_long(plaintext),)))
+
+ def _exercise_public_primitive(self, rsaObj):
+ plaintext = a2b_hex(self.plaintext)
+
+ # Test encryption (2 arguments)
+ (new_ciphertext2,) = rsaObj.encrypt(plaintext, b(""))
+
+ # Exercise verification
+ rsaObj.verify(new_ciphertext2, (bytes_to_long(plaintext),))
+
+ def _check_encryption(self, rsaObj):
+ plaintext = a2b_hex(self.plaintext)
+ ciphertext = a2b_hex(self.ciphertext)
+
+ # Test encryption (2 arguments)
+ (new_ciphertext2,) = rsaObj.encrypt(plaintext, b(""))
+ self.assertEqual(b2a_hex(ciphertext), b2a_hex(new_ciphertext2))
+
+ def _check_decryption(self, rsaObj):
+ plaintext = a2b_hex(self.plaintext)
+ ciphertext = a2b_hex(self.ciphertext)
+
+ # Test plain decryption
+ new_plaintext = rsaObj.decrypt((ciphertext,))
+ self.assertEqual(b2a_hex(plaintext), b2a_hex(new_plaintext))
+
+ # Test blinded decryption
+ blinding_factor = Random.new().read(len(ciphertext)-1)
+ blinded_ctext = rsaObj.blind(ciphertext, blinding_factor)
+ blinded_ptext = rsaObj.decrypt((blinded_ctext,))
+ unblinded_plaintext = rsaObj.unblind(blinded_ptext, blinding_factor)
+ self.assertEqual(b2a_hex(plaintext), b2a_hex(unblinded_plaintext))
+
+ def _check_verification(self, rsaObj):
+ signature = bytes_to_long(a2b_hex(self.plaintext))
+ message = a2b_hex(self.ciphertext)
+
+ # Test verification
+ t = (signature,) # rsaObj.verify expects a tuple
+ self.assertEqual(1, rsaObj.verify(message, t))
+
+ # Test verification with overlong tuple (this is a
+ # backward-compatibility hack to support some harmless misuse of the
+ # API)
+ t2 = (signature, '')
+ self.assertEqual(1, rsaObj.verify(message, t2)) # extra garbage at end of tuple
+
+ def _check_signing(self, rsaObj):
+ signature = bytes_to_long(a2b_hex(self.plaintext))
+ message = a2b_hex(self.ciphertext)
+
+ # Test signing (2 argument)
+ self.assertEqual((signature,), rsaObj.sign(message, b("")))
+
+class RSAFastMathTest(RSATest):
+ def setUp(self):
+ RSATest.setUp(self)
+ self.rsa = RSA.RSAImplementation(use_fast_math=True)
+
+ def test_generate_1arg(self):
+ """RSA (_fastmath implementation) generated key (1 argument)"""
+ RSATest.test_generate_1arg(self)
+
+ def test_generate_2arg(self):
+ """RSA (_fastmath implementation) generated key (2 arguments)"""
+ RSATest.test_generate_2arg(self)
+
+ def test_construct_2tuple(self):
+ """RSA (_fastmath implementation) constructed key (2-tuple)"""
+ RSATest.test_construct_2tuple(self)
+
+ def test_construct_3tuple(self):
+ """RSA (_fastmath implementation) constructed key (3-tuple)"""
+ RSATest.test_construct_3tuple(self)
+
+ def test_construct_4tuple(self):
+ """RSA (_fastmath implementation) constructed key (4-tuple)"""
+ RSATest.test_construct_4tuple(self)
+
+ def test_construct_5tuple(self):
+ """RSA (_fastmath implementation) constructed key (5-tuple)"""
+ RSATest.test_construct_5tuple(self)
+
+ def test_construct_6tuple(self):
+ """RSA (_fastmath implementation) constructed key (6-tuple)"""
+ RSATest.test_construct_6tuple(self)
+
+ def test_factoring(self):
+ RSATest.test_factoring(self)
+
+class RSASlowMathTest(RSATest):
+ def setUp(self):
+ RSATest.setUp(self)
+ self.rsa = RSA.RSAImplementation(use_fast_math=False)
+
+ def test_generate_1arg(self):
+ """RSA (_slowmath implementation) generated key (1 argument)"""
+ RSATest.test_generate_1arg(self)
+
+ def test_generate_2arg(self):
+ """RSA (_slowmath implementation) generated key (2 arguments)"""
+ RSATest.test_generate_2arg(self)
+
+ def test_construct_2tuple(self):
+ """RSA (_slowmath implementation) constructed key (2-tuple)"""
+ RSATest.test_construct_2tuple(self)
+
+ def test_construct_3tuple(self):
+ """RSA (_slowmath implementation) constructed key (3-tuple)"""
+ RSATest.test_construct_3tuple(self)
+
+ def test_construct_4tuple(self):
+ """RSA (_slowmath implementation) constructed key (4-tuple)"""
+ RSATest.test_construct_4tuple(self)
+
+ def test_construct_5tuple(self):
+ """RSA (_slowmath implementation) constructed key (5-tuple)"""
+ RSATest.test_construct_5tuple(self)
+
+ def test_construct_6tuple(self):
+ """RSA (_slowmath implementation) constructed key (6-tuple)"""
+ RSATest.test_construct_6tuple(self)
+
+ def test_factoring(self):
+ RSATest.test_factoring(self)
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(RSATest)
+ try:
+ from Crypto.PublicKey import _fastmath
+ tests += list_test_cases(RSAFastMathTest)
+ except ImportError:
+ from distutils.sysconfig import get_config_var
+ import inspect
+ _fm_path = os.path.normpath(os.path.dirname(os.path.abspath(
+ inspect.getfile(inspect.currentframe())))
+ +"/../../PublicKey/_fastmath"+get_config_var("SO"))
+ if os.path.exists(_fm_path):
+ raise ImportError("While the _fastmath module exists, importing "+
+ "it failed. This may point to the gmp or mpir shared library "+
+ "not being in the path. _fastmath was found at "+_fm_path)
+ if config.get('slow_tests',1):
+ tests += list_test_cases(RSASlowMathTest)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_importKey.py b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_importKey.py
new file mode 100644
index 000000000..28a7eee88
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/PublicKey/test_importKey.py
@@ -0,0 +1,345 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/PublicKey/test_importKey.py: Self-test for importing RSA keys
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+from __future__ import nested_scopes
+
+__revision__ = "$Id$"
+
+import unittest
+
+from Crypto.PublicKey import RSA
+from Crypto.SelfTest.st_common import *
+from Crypto.Util.py3compat import *
+from Crypto.Util.number import inverse
+from Crypto.Util import asn1
+
+def der2pem(der, text='PUBLIC'):
+ import binascii
+ chunks = [ binascii.b2a_base64(der[i:i+48]) for i in range(0, len(der), 48) ]
+ pem = b('-----BEGIN %s KEY-----\n' % text)
+ pem += b('').join(chunks)
+ pem += b('-----END %s KEY-----' % text)
+ return pem
+
+class ImportKeyTests(unittest.TestCase):
+ # 512-bit RSA key generated with openssl
+ rsaKeyPEM = u'''-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBAL8eJ5AKoIsjURpcEoGubZMxLD7+kT+TLr7UkvEtFrRhDDKMtuII
+q19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQJACUSDEp8RTe32ftq8IwG8
+Wojl5mAd1wFiIOrZ/Uv8b963WJOJiuQcVN29vxU5+My9GPZ7RA3hrDBEAoHUDPrI
+OQIhAPIPLz4dphiD9imAkivY31Rc5AfHJiQRA7XixTcjEkojAiEAyh/pJHks/Mlr
++rdPNEpotBjfV4M4BkgGAA/ipcmaAjcCIQCHvhwwKVBLzzTscT2HeUdEeBMoiXXK
+JACAr3sJQJGxIQIgarRp+m1WSKV1MciwMaTOnbU7wxFs9DP1pva76lYBzgUCIQC9
+n0CnZCJ6IZYqSt0H5N7+Q+2Ro64nuwV/OSQfM6sBwQ==
+-----END RSA PRIVATE KEY-----'''
+
+ # As above, but this is actually an unencrypted PKCS#8 key
+ rsaKeyPEM8 = u'''-----BEGIN PRIVATE KEY-----
+MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvx4nkAqgiyNRGlwS
+ga5tkzEsPv6RP5MuvtSS8S0WtGEMMoy24girX0WsvilQgzKY8xIsGfeEkt7fQPDj
+wZAzhQIDAQABAkAJRIMSnxFN7fZ+2rwjAbxaiOXmYB3XAWIg6tn9S/xv3rdYk4mK
+5BxU3b2/FTn4zL0Y9ntEDeGsMEQCgdQM+sg5AiEA8g8vPh2mGIP2KYCSK9jfVFzk
+B8cmJBEDteLFNyMSSiMCIQDKH+kkeSz8yWv6t080Smi0GN9XgzgGSAYAD+KlyZoC
+NwIhAIe+HDApUEvPNOxxPYd5R0R4EyiJdcokAICvewlAkbEhAiBqtGn6bVZIpXUx
+yLAxpM6dtTvDEWz0M/Wm9rvqVgHOBQIhAL2fQKdkInohlipK3Qfk3v5D7ZGjrie7
+BX85JB8zqwHB
+-----END PRIVATE KEY-----'''
+
+ # The same RSA private key as in rsaKeyPEM, but now encrypted
+ rsaKeyEncryptedPEM=(
+
+ # With DES and passphrase 'test'
+ ('test', u'''-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-CBC,AF8F9A40BD2FA2FC
+
+Ckl9ex1kaVEWhYC2QBmfaF+YPiR4NFkRXA7nj3dcnuFEzBnY5XULupqQpQI3qbfA
+u8GYS7+b3toWWiHZivHbAAUBPDIZG9hKDyB9Sq2VMARGsX1yW1zhNvZLIiVJzUHs
+C6NxQ1IJWOXzTew/xM2I26kPwHIvadq+/VaT8gLQdjdH0jOiVNaevjWnLgrn1mLP
+BCNRMdcexozWtAFNNqSzfW58MJL2OdMi21ED184EFytIc1BlB+FZiGZduwKGuaKy
+9bMbdb/1PSvsSzPsqW7KSSrTw6MgJAFJg6lzIYvR5F4poTVBxwBX3+EyEmShiaNY
+IRX3TgQI0IjrVuLmvlZKbGWP18FXj7I7k9tSsNOOzllTTdq3ny5vgM3A+ynfAaxp
+dysKznQ6P+IoqML1WxAID4aGRMWka+uArOJ148Rbj9s=
+-----END RSA PRIVATE KEY-----''',
+ "\xAF\x8F\x9A\x40\xBD\x2F\xA2\xFC"),
+
+ # With Triple-DES and passphrase 'rocking'
+ ('rocking', u'''-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,C05D6C07F7FC02F6
+
+w4lwQrXaVoTTJ0GgwY566htTA2/t1YlimhxkxYt9AEeCcidS5M0Wq9ClPiPz9O7F
+m6K5QpM1rxo1RUE/ZyI85gglRNPdNwkeTOqit+kum7nN73AToX17+irVmOA4Z9E+
+4O07t91GxGMcjUSIFk0ucwEU4jgxRvYscbvOMvNbuZszGdVNzBTVddnShKCsy9i7
+nJbPlXeEKYi/OkRgO4PtfqqWQu5GIEFVUf9ev1QV7AvC+kyWTR1wWYnHX265jU5c
+sopxQQtP8XEHIJEdd5/p1oieRcWTCNyY8EkslxDSsrf0OtZp6mZH9N+KU47cgQtt
+9qGORmlWnsIoFFKcDohbtOaWBTKhkj5h6OkLjFjfU/sBeV1c+7wDT3dAy5tawXjG
+YSxC7qDQIT/RECvV3+oQKEcmpEujn45wAnkTi12BH30=
+-----END RSA PRIVATE KEY-----''',
+ "\xC0\x5D\x6C\x07\xF7\xFC\x02\xF6"),
+ )
+
+ rsaPublicKeyPEM = u'''-----BEGIN PUBLIC KEY-----
+MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL8eJ5AKoIsjURpcEoGubZMxLD7+kT+T
+Lr7UkvEtFrRhDDKMtuIIq19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQ==
+-----END PUBLIC KEY-----'''
+
+ # Obtained using 'ssh-keygen -i -m PKCS8 -f rsaPublicKeyPEM'
+ rsaPublicKeyOpenSSH = '''ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQC/HieQCqCLI1EaXBKBrm2TMSw+/pE/ky6+1JLxLRa0YQwyjLbiCKtfRay+KVCDMpjzEiwZ94SS3t9A8OPBkDOF comment\n'''
+
+ # The private key, in PKCS#1 format encoded with DER
+ rsaKeyDER = a2b_hex(
+ '''3082013b020100024100bf1e27900aa08b23511a5c1281ae6d93312c3efe
+ 913f932ebed492f12d16b4610c328cb6e208ab5f45acbe2950833298f312
+ 2c19f78492dedf40f0e3c190338502030100010240094483129f114dedf6
+ 7edabc2301bc5a88e5e6601dd7016220ead9fd4bfc6fdeb75893898ae41c
+ 54ddbdbf1539f8ccbd18f67b440de1ac30440281d40cfac839022100f20f
+ 2f3e1da61883f62980922bd8df545ce407c726241103b5e2c53723124a23
+ 022100ca1fe924792cfcc96bfab74f344a68b418df578338064806000fe2
+ a5c99a023702210087be1c3029504bcf34ec713d877947447813288975ca
+ 240080af7b094091b12102206ab469fa6d5648a57531c8b031a4ce9db53b
+ c3116cf433f5a6f6bbea5601ce05022100bd9f40a764227a21962a4add07
+ e4defe43ed91a3ae27bb057f39241f33ab01c1
+ '''.replace(" ",""))
+
+ # The private key, in unencrypted PKCS#8 format encoded with DER
+ rsaKeyDER8 = a2b_hex(
+ '''30820155020100300d06092a864886f70d01010105000482013f3082013
+ b020100024100bf1e27900aa08b23511a5c1281ae6d93312c3efe913f932
+ ebed492f12d16b4610c328cb6e208ab5f45acbe2950833298f3122c19f78
+ 492dedf40f0e3c190338502030100010240094483129f114dedf67edabc2
+ 301bc5a88e5e6601dd7016220ead9fd4bfc6fdeb75893898ae41c54ddbdb
+ f1539f8ccbd18f67b440de1ac30440281d40cfac839022100f20f2f3e1da
+ 61883f62980922bd8df545ce407c726241103b5e2c53723124a23022100c
+ a1fe924792cfcc96bfab74f344a68b418df578338064806000fe2a5c99a0
+ 23702210087be1c3029504bcf34ec713d877947447813288975ca240080a
+ f7b094091b12102206ab469fa6d5648a57531c8b031a4ce9db53bc3116cf
+ 433f5a6f6bbea5601ce05022100bd9f40a764227a21962a4add07e4defe4
+ 3ed91a3ae27bb057f39241f33ab01c1
+ '''.replace(" ",""))
+
+ rsaPublicKeyDER = a2b_hex(
+ '''305c300d06092a864886f70d0101010500034b003048024100bf1e27900a
+ a08b23511a5c1281ae6d93312c3efe913f932ebed492f12d16b4610c328c
+ b6e208ab5f45acbe2950833298f3122c19f78492dedf40f0e3c190338502
+ 03010001
+ '''.replace(" ",""))
+
+ n = long('BF 1E 27 90 0A A0 8B 23 51 1A 5C 12 81 AE 6D 93 31 2C 3E FE 91 3F 93 2E BE D4 92 F1 2D 16 B4 61 0C 32 8C B6 E2 08 AB 5F 45 AC BE 29 50 83 32 98 F3 12 2C 19 F7 84 92 DE DF 40 F0 E3 C1 90 33 85'.replace(" ",""),16)
+ e = 65537L
+ d = long('09 44 83 12 9F 11 4D ED F6 7E DA BC 23 01 BC 5A 88 E5 E6 60 1D D7 01 62 20 EA D9 FD 4B FC 6F DE B7 58 93 89 8A E4 1C 54 DD BD BF 15 39 F8 CC BD 18 F6 7B 44 0D E1 AC 30 44 02 81 D4 0C FA C8 39'.replace(" ",""),16)
+ p = long('00 F2 0F 2F 3E 1D A6 18 83 F6 29 80 92 2B D8 DF 54 5C E4 07 C7 26 24 11 03 B5 E2 C5 37 23 12 4A 23'.replace(" ",""),16)
+ q = long('00 CA 1F E9 24 79 2C FC C9 6B FA B7 4F 34 4A 68 B4 18 DF 57 83 38 06 48 06 00 0F E2 A5 C9 9A 02 37'.replace(" ",""),16)
+
+ # This is q^{-1} mod p). fastmath and slowmath use pInv (p^{-1}
+ # mod q) instead!
+ qInv = long('00 BD 9F 40 A7 64 22 7A 21 96 2A 4A DD 07 E4 DE FE 43 ED 91 A3 AE 27 BB 05 7F 39 24 1F 33 AB 01 C1'.replace(" ",""),16)
+ pInv = inverse(p,q)
+
+ def testImportKey1(self):
+ """Verify import of RSAPrivateKey DER SEQUENCE"""
+ key = self.rsa.importKey(self.rsaKeyDER)
+ self.failUnless(key.has_private())
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+ self.assertEqual(key.d, self.d)
+ self.assertEqual(key.p, self.p)
+ self.assertEqual(key.q, self.q)
+
+ def testImportKey2(self):
+ """Verify import of SubjectPublicKeyInfo DER SEQUENCE"""
+ key = self.rsa.importKey(self.rsaPublicKeyDER)
+ self.failIf(key.has_private())
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+
+ def testImportKey3unicode(self):
+ """Verify import of RSAPrivateKey DER SEQUENCE, encoded with PEM as unicode"""
+ key = RSA.importKey(self.rsaKeyPEM)
+ self.assertEqual(key.has_private(),True) # assert_
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+ self.assertEqual(key.d, self.d)
+ self.assertEqual(key.p, self.p)
+ self.assertEqual(key.q, self.q)
+
+ def testImportKey3bytes(self):
+ """Verify import of RSAPrivateKey DER SEQUENCE, encoded with PEM as byte string"""
+ key = RSA.importKey(b(self.rsaKeyPEM))
+ self.assertEqual(key.has_private(),True) # assert_
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+ self.assertEqual(key.d, self.d)
+ self.assertEqual(key.p, self.p)
+ self.assertEqual(key.q, self.q)
+
+ def testImportKey4unicode(self):
+ """Verify import of RSAPrivateKey DER SEQUENCE, encoded with PEM as unicode"""
+ key = RSA.importKey(self.rsaPublicKeyPEM)
+ self.assertEqual(key.has_private(),False) # failIf
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+
+ def testImportKey4bytes(self):
+ """Verify import of SubjectPublicKeyInfo DER SEQUENCE, encoded with PEM as byte string"""
+ key = RSA.importKey(b(self.rsaPublicKeyPEM))
+ self.assertEqual(key.has_private(),False) # failIf
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+
+ def testImportKey5(self):
+ """Verifies that the imported key is still a valid RSA pair"""
+ key = RSA.importKey(self.rsaKeyPEM)
+ idem = key.encrypt(key.decrypt(b("Test")),0)
+ self.assertEqual(idem[0],b("Test"))
+
+ def testImportKey6(self):
+ """Verifies that the imported key is still a valid RSA pair"""
+ key = RSA.importKey(self.rsaKeyDER)
+ idem = key.encrypt(key.decrypt(b("Test")),0)
+ self.assertEqual(idem[0],b("Test"))
+
+ def testImportKey7(self):
+ """Verify import of OpenSSH public key"""
+ key = self.rsa.importKey(self.rsaPublicKeyOpenSSH)
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+
+ def testImportKey8(self):
+ """Verify import of encrypted PrivateKeyInfo DER SEQUENCE"""
+ for t in self.rsaKeyEncryptedPEM:
+ key = self.rsa.importKey(t[1], t[0])
+ self.failUnless(key.has_private())
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+ self.assertEqual(key.d, self.d)
+ self.assertEqual(key.p, self.p)
+ self.assertEqual(key.q, self.q)
+
+ def testImportKey9(self):
+ """Verify import of unencrypted PrivateKeyInfo DER SEQUENCE"""
+ key = self.rsa.importKey(self.rsaKeyDER8)
+ self.failUnless(key.has_private())
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+ self.assertEqual(key.d, self.d)
+ self.assertEqual(key.p, self.p)
+ self.assertEqual(key.q, self.q)
+
+ def testImportKey10(self):
+ """Verify import of unencrypted PrivateKeyInfo DER SEQUENCE, encoded with PEM"""
+ key = self.rsa.importKey(self.rsaKeyPEM8)
+ self.failUnless(key.has_private())
+ self.assertEqual(key.n, self.n)
+ self.assertEqual(key.e, self.e)
+ self.assertEqual(key.d, self.d)
+ self.assertEqual(key.p, self.p)
+ self.assertEqual(key.q, self.q)
+
+ def testImportKey11(self):
+ """Verify import of RSAPublicKey DER SEQUENCE"""
+ der = asn1.DerSequence([17, 3]).encode()
+ key = self.rsa.importKey(der)
+ self.assertEqual(key.n, 17)
+ self.assertEqual(key.e, 3)
+
+ def testImportKey12(self):
+ """Verify import of RSAPublicKey DER SEQUENCE, encoded with PEM"""
+ der = asn1.DerSequence([17, 3]).encode()
+ pem = der2pem(der)
+ key = self.rsa.importKey(pem)
+ self.assertEqual(key.n, 17)
+ self.assertEqual(key.e, 3)
+
+ ###
+ def testExportKey1(self):
+ key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv])
+ derKey = key.exportKey("DER")
+ self.assertEqual(derKey, self.rsaKeyDER)
+
+ def testExportKey2(self):
+ key = self.rsa.construct([self.n, self.e])
+ derKey = key.exportKey("DER")
+ self.assertEqual(derKey, self.rsaPublicKeyDER)
+
+ def testExportKey3(self):
+ key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv])
+ pemKey = key.exportKey("PEM")
+ self.assertEqual(pemKey, b(self.rsaKeyPEM))
+
+ def testExportKey4(self):
+ key = self.rsa.construct([self.n, self.e])
+ pemKey = key.exportKey("PEM")
+ self.assertEqual(pemKey, b(self.rsaPublicKeyPEM))
+
+ def testExportKey5(self):
+ key = self.rsa.construct([self.n, self.e])
+ openssh_1 = key.exportKey("OpenSSH").split()
+ openssh_2 = self.rsaPublicKeyOpenSSH.split()
+ self.assertEqual(openssh_1[0], openssh_2[0])
+ self.assertEqual(openssh_1[1], openssh_2[1])
+
+ def testExportKey4(self):
+ key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv])
+ # Tuple with index #1 is encrypted with 3DES
+ t = map(b,self.rsaKeyEncryptedPEM[1])
+ # Force the salt being used when exporting
+ key._randfunc = lambda N: (t[2]*divmod(N+len(t[2]),len(t[2]))[0])[:N]
+ pemKey = key.exportKey("PEM", t[0])
+ self.assertEqual(pemKey, t[1])
+
+ def testExportKey5(self):
+ key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv])
+ derKey = key.exportKey("DER", pkcs=8)
+ self.assertEqual(derKey, self.rsaKeyDER8)
+
+ def testExportKey6(self):
+ key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv])
+ pemKey = key.exportKey("PEM", pkcs=8)
+ self.assertEqual(pemKey, b(self.rsaKeyPEM8))
+
+class ImportKeyTestsSlow(ImportKeyTests):
+ def setUp(self):
+ self.rsa = RSA.RSAImplementation(use_fast_math=0)
+
+class ImportKeyTestsFast(ImportKeyTests):
+ def setUp(self):
+ self.rsa = RSA.RSAImplementation(use_fast_math=1)
+
+if __name__ == '__main__':
+ unittest.main()
+
+def get_tests(config={}):
+ tests = []
+ try:
+ from Crypto.PublicKey import _fastmath
+ tests += list_test_cases(ImportKeyTestsFast)
+ except ImportError:
+ pass
+ tests += list_test_cases(ImportKeyTestsSlow)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/__init__.py
new file mode 100644
index 000000000..81a0e13c1
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/__init__.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Random/Fortuna/__init__.py: Self-test for Fortuna modules
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for the Crypto.Random.Fortuna package"""
+
+__revision__ = "$Id$"
+
+import os
+
+def get_tests(config={}):
+ tests = []
+ from Crypto.SelfTest.Random.Fortuna import test_FortunaAccumulator; tests += test_FortunaAccumulator.get_tests(config=config)
+ from Crypto.SelfTest.Random.Fortuna import test_FortunaGenerator; tests += test_FortunaGenerator.get_tests(config=config)
+ from Crypto.SelfTest.Random.Fortuna import test_SHAd256; tests += test_SHAd256.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py
new file mode 100644
index 000000000..c4e6ccf2b
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Random/Fortuna/test_FortunaAccumulator.py: Self-test for the FortunaAccumulator module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-tests for Crypto.Random.Fortuna.FortunaAccumulator"""
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+import unittest
+from binascii import b2a_hex
+
+class FortunaAccumulatorTests(unittest.TestCase):
+ def setUp(self):
+ global FortunaAccumulator
+ from Crypto.Random.Fortuna import FortunaAccumulator
+
+ def test_FortunaPool(self):
+ """FortunaAccumulator.FortunaPool"""
+ pool = FortunaAccumulator.FortunaPool()
+ self.assertEqual(0, pool.length)
+ self.assertEqual("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456", pool.hexdigest())
+
+ pool.append(b('abc'))
+
+ self.assertEqual(3, pool.length)
+ self.assertEqual("4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358", pool.hexdigest())
+
+ pool.append(b("dbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"))
+
+ self.assertEqual(56, pool.length)
+ self.assertEqual(b('0cffe17f68954dac3a84fb1458bd5ec99209449749b2b308b7cb55812f9563af'), b2a_hex(pool.digest()))
+
+ pool.reset()
+
+ self.assertEqual(0, pool.length)
+
+ pool.append(b('a') * 10**6)
+
+ self.assertEqual(10**6, pool.length)
+ self.assertEqual(b('80d1189477563e1b5206b2749f1afe4807e5705e8bd77887a60187a712156688'), b2a_hex(pool.digest()))
+
+ def test_which_pools(self):
+ """FortunaAccumulator.which_pools"""
+
+ # which_pools(0) should fail
+ self.assertRaises(AssertionError, FortunaAccumulator.which_pools, 0)
+
+ self.assertEqual(FortunaAccumulator.which_pools(1), [0])
+ self.assertEqual(FortunaAccumulator.which_pools(2), [0, 1])
+ self.assertEqual(FortunaAccumulator.which_pools(3), [0])
+ self.assertEqual(FortunaAccumulator.which_pools(4), [0, 1, 2])
+ self.assertEqual(FortunaAccumulator.which_pools(5), [0])
+ self.assertEqual(FortunaAccumulator.which_pools(6), [0, 1])
+ self.assertEqual(FortunaAccumulator.which_pools(7), [0])
+ self.assertEqual(FortunaAccumulator.which_pools(8), [0, 1, 2, 3])
+ for i in range(1, 32):
+ self.assertEqual(FortunaAccumulator.which_pools(2L**i-1), [0])
+ self.assertEqual(FortunaAccumulator.which_pools(2L**i), range(i+1))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**i+1), [0])
+ self.assertEqual(FortunaAccumulator.which_pools(2L**31), range(32))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**32), range(32))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**33), range(32))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**34), range(32))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**35), range(32))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**36), range(32))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**64), range(32))
+ self.assertEqual(FortunaAccumulator.which_pools(2L**128), range(32))
+
+ def test_accumulator(self):
+ """FortunaAccumulator.FortunaAccumulator"""
+ fa = FortunaAccumulator.FortunaAccumulator()
+
+ # This should fail, because we haven't seeded the PRNG yet
+ self.assertRaises(AssertionError, fa.random_data, 1)
+
+ # Spread some test data across the pools (source number 42)
+ # This would be horribly insecure in a real system.
+ for p in range(32):
+ fa.add_random_event(42, p, b("X") * 32)
+ self.assertEqual(32+2, fa.pools[p].length)
+
+ # This should still fail, because we haven't seeded the PRNG with 64 bytes yet
+ self.assertRaises(AssertionError, fa.random_data, 1)
+
+ # Add more data
+ for p in range(32):
+ fa.add_random_event(42, p, b("X") * 32)
+ self.assertEqual((32+2)*2, fa.pools[p].length)
+
+ # The underlying RandomGenerator should get seeded with Pool 0
+ # s = SHAd256(chr(42) + chr(32) + "X"*32 + chr(42) + chr(32) + "X"*32)
+ # = SHA256(h'edd546f057b389155a31c32e3975e736c1dec030ddebb137014ecbfb32ed8c6f')
+ # = h'aef42a5dcbddab67e8efa118e1b47fde5d697f89beb971b99e6e8e5e89fbf064'
+ # The counter and the key before reseeding is:
+ # C_0 = 0
+ # K_0 = "\x00" * 32
+ # The counter after reseeding is 1, and the new key after reseeding is
+ # C_1 = 1
+ # K_1 = SHAd256(K_0 || s)
+ # = SHA256(h'0eae3e401389fab86640327ac919ecfcb067359d95469e18995ca889abc119a6')
+ # = h'aafe9d0409fbaaafeb0a1f2ef2014a20953349d3c1c6e6e3b962953bea6184dd'
+ # The first block of random data, therefore, is
+ # r_1 = AES-256(K_1, 1)
+ # = AES-256(K_1, h'01000000000000000000000000000000')
+ # = h'b7b86bd9a27d96d7bb4add1b6b10d157'
+ # The second block of random data is
+ # r_2 = AES-256(K_1, 2)
+ # = AES-256(K_1, h'02000000000000000000000000000000')
+ # = h'2350b1c61253db2f8da233be726dc15f'
+ # The third and fourth blocks of random data (which become the new key) are
+ # r_3 = AES-256(K_1, 3)
+ # = AES-256(K_1, h'03000000000000000000000000000000')
+ # = h'f23ad749f33066ff53d307914fbf5b21'
+ # r_4 = AES-256(K_1, 4)
+ # = AES-256(K_1, h'04000000000000000000000000000000')
+ # = h'da9667c7e86ba247655c9490e9d94a7c'
+ # K_2 = r_3 || r_4
+ # = h'f23ad749f33066ff53d307914fbf5b21da9667c7e86ba247655c9490e9d94a7c'
+ # The final counter value is 5.
+ self.assertEqual("aef42a5dcbddab67e8efa118e1b47fde5d697f89beb971b99e6e8e5e89fbf064",
+ fa.pools[0].hexdigest())
+ self.assertEqual(None, fa.generator.key)
+ self.assertEqual(0, fa.generator.counter.next_value())
+
+ result = fa.random_data(32)
+
+ self.assertEqual(b("b7b86bd9a27d96d7bb4add1b6b10d157" "2350b1c61253db2f8da233be726dc15f"), b2a_hex(result))
+ self.assertEqual(b("f23ad749f33066ff53d307914fbf5b21da9667c7e86ba247655c9490e9d94a7c"), b2a_hex(fa.generator.key))
+ self.assertEqual(5, fa.generator.counter.next_value())
+
+ def test_accumulator_pool_length(self):
+ """FortunaAccumulator.FortunaAccumulator minimum pool length"""
+ fa = FortunaAccumulator.FortunaAccumulator()
+
+ # This test case is hard-coded to assume that FortunaAccumulator.min_pool_size is 64.
+ self.assertEqual(fa.min_pool_size, 64)
+
+ # The PRNG should not allow us to get random data from it yet
+ self.assertRaises(AssertionError, fa.random_data, 1)
+
+ # Add 60 bytes, 4 at a time (2 header + 2 payload) to each of the 32 pools
+ for i in range(15):
+ for p in range(32):
+ # Add the bytes to the pool
+ fa.add_random_event(2, p, b("XX"))
+
+ # The PRNG should not allow us to get random data from it yet
+ self.assertRaises(AssertionError, fa.random_data, 1)
+
+ # Add 4 more bytes to pool 0
+ fa.add_random_event(2, 0, b("XX"))
+
+ # We should now be able to get data from the accumulator
+ fa.random_data(1)
+
+def get_tests(config={}):
+ from Crypto.SelfTest.st_common import list_test_cases
+ return list_test_cases(FortunaAccumulatorTests)
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py
new file mode 100644
index 000000000..d41bb022a
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Random/Fortuna/test_FortunaGenerator.py: Self-test for the FortunaGenerator module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-tests for Crypto.Random.Fortuna.FortunaGenerator"""
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+import unittest
+from binascii import b2a_hex
+
+class FortunaGeneratorTests(unittest.TestCase):
+ def setUp(self):
+ global FortunaGenerator
+ from Crypto.Random.Fortuna import FortunaGenerator
+
+ def test_generator(self):
+ """FortunaGenerator.AESGenerator"""
+ fg = FortunaGenerator.AESGenerator()
+
+ # We shouldn't be able to read data until we've seeded the generator
+ self.assertRaises(Exception, fg.pseudo_random_data, 1)
+ self.assertEqual(0, fg.counter.next_value())
+
+ # Seed the generator, which should set the key and increment the counter.
+ fg.reseed(b("Hello"))
+ self.assertEqual(b("0ea6919d4361551364242a4ba890f8f073676e82cf1a52bb880f7e496648b565"), b2a_hex(fg.key))
+ self.assertEqual(1, fg.counter.next_value())
+
+ # Read 2 full blocks from the generator
+ self.assertEqual(b("7cbe2c17684ac223d08969ee8b565616") + # counter=1
+ b("717661c0d2f4758bd6ba140bf3791abd"), # counter=2
+ b2a_hex(fg.pseudo_random_data(32)))
+
+ # Meanwhile, the generator will have re-keyed itself and incremented its counter
+ self.assertEqual(b("33a1bb21987859caf2bbfc5615bef56d") + # counter=3
+ b("e6b71ff9f37112d0c193a135160862b7"), # counter=4
+ b2a_hex(fg.key))
+ self.assertEqual(5, fg.counter.next_value())
+
+ # Read another 2 blocks from the generator
+ self.assertEqual(b("fd6648ba3086e919cee34904ef09a7ff") + # counter=5
+ b("021f77580558b8c3e9248275f23042bf"), # counter=6
+ b2a_hex(fg.pseudo_random_data(32)))
+
+
+ # Try to read more than 2**20 bytes using the internal function. This should fail.
+ self.assertRaises(AssertionError, fg._pseudo_random_data, 2**20+1)
+
+def get_tests(config={}):
+ from Crypto.SelfTest.st_common import list_test_cases
+ return list_test_cases(FortunaGeneratorTests)
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py
new file mode 100644
index 000000000..f94db8a9b
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Random/Fortuna/test_SHAd256.py: Self-test for the SHAd256 hash function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Random.Fortuna.SHAd256"""
+
+__revision__ = "$Id$"
+from Crypto.Util.py3compat import *
+
+# This is a list of (expected_result, input[, description]) tuples.
+test_data = [
+ # I could not find any test vectors for SHAd256, so I made these vectors by
+ # feeding some sample data into several plain SHA256 implementations
+ # (including OpenSSL, the "sha256sum" tool, and this implementation).
+ # This is a subset of the resulting test vectors. The complete list can be
+ # found at: http://www.dlitz.net/crypto/shad256-test-vectors/
+ ('5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456',
+ '', "'' (empty string)"),
+ ('4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358',
+ 'abc'),
+ ('0cffe17f68954dac3a84fb1458bd5ec99209449749b2b308b7cb55812f9563af',
+ 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')
+]
+
+def get_tests(config={}):
+ from Crypto.Random.Fortuna import SHAd256
+ from Crypto.SelfTest.Hash.common import make_hash_tests
+ return make_hash_tests(SHAd256, "SHAd256", test_data, 32)
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/__init__.py
new file mode 100644
index 000000000..44b3fa157
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/__init__.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Random/OSRNG/__init__.py: Self-test for OSRNG modules
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for Crypto.Random.OSRNG package"""
+
+__revision__ = "$Id$"
+
+import os
+
+def get_tests(config={}):
+ tests = []
+ if os.name == 'nt':
+ from Crypto.SelfTest.Random.OSRNG import test_nt; tests += test_nt.get_tests(config=config)
+ from Crypto.SelfTest.Random.OSRNG import test_winrandom; tests += test_winrandom.get_tests(config=config)
+ elif os.name == 'posix':
+ from Crypto.SelfTest.Random.OSRNG import test_posix; tests += test_posix.get_tests(config=config)
+ if hasattr(os, 'urandom'):
+ from Crypto.SelfTest.Random.OSRNG import test_fallback; tests += test_fallback.get_tests(config=config)
+ from Crypto.SelfTest.Random.OSRNG import test_generic; tests += test_generic.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py
new file mode 100644
index 000000000..41909b0a4
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_fallback.py: Self-test for the OSRNG.fallback.new() function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Random.OSRNG.fallback"""
+
+__revision__ = "$Id$"
+
+import unittest
+
+class SimpleTest(unittest.TestCase):
+ def runTest(self):
+ """Crypto.Random.OSRNG.fallback.new()"""
+ # Import the OSRNG.nt module and try to use it
+ import Crypto.Random.OSRNG.fallback
+ randobj = Crypto.Random.OSRNG.fallback.new()
+ x = randobj.read(16)
+ y = randobj.read(16)
+ self.assertNotEqual(x, y)
+
+def get_tests(config={}):
+ return [SimpleTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_generic.py b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_generic.py
new file mode 100644
index 000000000..2a409741e
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_generic.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_generic.py: Self-test for the OSRNG.new() function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Random.OSRNG"""
+
+__revision__ = "$Id$"
+
+import unittest
+
+class SimpleTest(unittest.TestCase):
+ def runTest(self):
+ """Crypto.Random.OSRNG.new()"""
+ # Import the OSRNG module and try to use it
+ import Crypto.Random.OSRNG
+ randobj = Crypto.Random.OSRNG.new()
+ x = randobj.read(16)
+ y = randobj.read(16)
+ self.assertNotEqual(x, y)
+
+def get_tests(config={}):
+ return [SimpleTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_nt.py b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_nt.py
new file mode 100644
index 000000000..a7a833859
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_nt.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_generic.py: Self-test for the OSRNG.nt.new() function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Random.OSRNG.nt"""
+
+__revision__ = "$Id$"
+
+import unittest
+
+class SimpleTest(unittest.TestCase):
+ def runTest(self):
+ """Crypto.Random.OSRNG.nt.new()"""
+ # Import the OSRNG.nt module and try to use it
+ import Crypto.Random.OSRNG.nt
+ randobj = Crypto.Random.OSRNG.nt.new()
+ x = randobj.read(16)
+ y = randobj.read(16)
+ self.assertNotEqual(x, y)
+
+def get_tests(config={}):
+ return [SimpleTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_posix.py b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_posix.py
new file mode 100644
index 000000000..2224afe61
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_posix.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_posix.py: Self-test for the OSRNG.posix.new() function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Random.OSRNG.posix"""
+
+__revision__ = "$Id$"
+
+import unittest
+
+class SimpleTest(unittest.TestCase):
+ def runTest(self):
+ """Crypto.Random.OSRNG.posix.new()"""
+ # Import the OSRNG.nt module and try to use it
+ import Crypto.Random.OSRNG.posix
+ randobj = Crypto.Random.OSRNG.posix.new()
+ x = randobj.read(16)
+ y = randobj.read(16)
+ self.assertNotEqual(x, y)
+
+def get_tests(config={}):
+ return [SimpleTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py
new file mode 100644
index 000000000..3010eb725
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_winrandom.py: Self-test for the winrandom module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Random.OSRNG.winrandom"""
+
+__revision__ = "$Id$"
+
+import unittest
+
+class SimpleTest(unittest.TestCase):
+ def runTest(self):
+ """Crypto.Random.OSRNG.winrandom"""
+ # Import the winrandom module and try to use it
+ from Crypto.Random.OSRNG import winrandom
+ randobj = winrandom.new()
+ x = randobj.get_bytes(16)
+ y = randobj.get_bytes(16)
+ self.assertNotEqual(x, y)
+
+def get_tests(config={}):
+ return [SimpleTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Random/__init__.py
new file mode 100644
index 000000000..f972bf0bf
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/__init__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Random/__init__.py: Self-test for random number generation modules
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for random number generators"""
+
+__revision__ = "$Id$"
+
+def get_tests(config={}):
+ tests = []
+ from Crypto.SelfTest.Random import Fortuna; tests += Fortuna.get_tests(config=config)
+ from Crypto.SelfTest.Random import OSRNG; tests += OSRNG.get_tests(config=config)
+ from Crypto.SelfTest.Random import test_random; tests += test_random.get_tests(config=config)
+ from Crypto.SelfTest.Random import test_rpoolcompat; tests += test_rpoolcompat.get_tests(config=config)
+ from Crypto.SelfTest.Random import test__UserFriendlyRNG; tests += test__UserFriendlyRNG.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/test__UserFriendlyRNG.py b/lib/Python/Lib/Crypto/SelfTest/Random/test__UserFriendlyRNG.py
new file mode 100644
index 000000000..771a6635d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/test__UserFriendlyRNG.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+# Self-tests for the user-friendly Crypto.Random interface
+#
+# Written in 2013 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for generic Crypto.Random stuff """
+
+from __future__ import nested_scopes
+
+__revision__ = "$Id$"
+
+import binascii
+import pprint
+import unittest
+import os
+import time
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+try:
+ import multiprocessing
+except ImportError:
+ multiprocessing = None
+
+import Crypto.Random._UserFriendlyRNG
+import Crypto.Random.random
+
+class RNGForkTest(unittest.TestCase):
+
+ def _get_reseed_count(self):
+ """
+ Get `FortunaAccumulator.reseed_count`, the global count of the
+ number of times that the PRNG has been reseeded.
+ """
+ rng_singleton = Crypto.Random._UserFriendlyRNG._get_singleton()
+ rng_singleton._lock.acquire()
+ try:
+ return rng_singleton._fa.reseed_count
+ finally:
+ rng_singleton._lock.release()
+
+ def runTest(self):
+ # Regression test for CVE-2013-1445. We had a bug where, under the
+ # right conditions, two processes might see the same random sequence.
+
+ if sys.platform.startswith('win'): # windows can't fork
+ assert not hasattr(os, 'fork') # ... right?
+ return
+
+ # Wait 150 ms so that we don't trigger the rate-limit prematurely.
+ time.sleep(0.15)
+
+ reseed_count_before = self._get_reseed_count()
+
+ # One or both of these calls together should trigger a reseed right here.
+ Crypto.Random._UserFriendlyRNG._get_singleton().reinit()
+ Crypto.Random.get_random_bytes(1)
+
+ reseed_count_after = self._get_reseed_count()
+ self.assertNotEqual(reseed_count_before, reseed_count_after) # sanity check: test should reseed parent before forking
+
+ rfiles = []
+ for i in range(10):
+ rfd, wfd = os.pipe()
+ if os.fork() == 0:
+ # child
+ os.close(rfd)
+ f = os.fdopen(wfd, "wb")
+
+ Crypto.Random.atfork()
+
+ data = Crypto.Random.get_random_bytes(16)
+
+ f.write(data)
+ f.close()
+ os._exit(0)
+ # parent
+ os.close(wfd)
+ rfiles.append(os.fdopen(rfd, "rb"))
+
+ results = []
+ results_dict = {}
+ for f in rfiles:
+ data = binascii.hexlify(f.read())
+ results.append(data)
+ results_dict[data] = 1
+ f.close()
+
+ if len(results) != len(results_dict.keys()):
+ raise AssertionError("RNG output duplicated across fork():\n%s" %
+ (pprint.pformat(results)))
+
+
+# For RNGMultiprocessingForkTest
+def _task_main(q):
+ a = Crypto.Random.get_random_bytes(16)
+ time.sleep(0.1) # wait 100 ms
+ b = Crypto.Random.get_random_bytes(16)
+ q.put(binascii.b2a_hex(a))
+ q.put(binascii.b2a_hex(b))
+ q.put(None) # Wait for acknowledgment
+
+
+class RNGMultiprocessingForkTest(unittest.TestCase):
+
+ def runTest(self):
+ # Another regression test for CVE-2013-1445. This is basically the
+ # same as RNGForkTest, but less compatible with old versions of Python,
+ # and a little easier to read.
+
+ n_procs = 5
+ manager = multiprocessing.Manager()
+ queues = [manager.Queue(1) for i in range(n_procs)]
+
+ # Reseed the pool
+ time.sleep(0.15)
+ Crypto.Random._UserFriendlyRNG._get_singleton().reinit()
+ Crypto.Random.get_random_bytes(1)
+
+ # Start the child processes
+ pool = multiprocessing.Pool(processes=n_procs, initializer=Crypto.Random.atfork)
+ map_result = pool.map_async(_task_main, queues)
+
+ # Get the results, ensuring that no pool processes are reused.
+ aa = [queues[i].get(30) for i in range(n_procs)]
+ bb = [queues[i].get(30) for i in range(n_procs)]
+ res = list(zip(aa, bb))
+
+ # Shut down the pool
+ map_result.get(30)
+ pool.close()
+ pool.join()
+
+ # Check that the results are unique
+ if len(set(aa)) != len(aa) or len(set(res)) != len(res):
+ raise AssertionError("RNG output duplicated across fork():\n%s" %
+ (pprint.pformat(res),))
+
+
+def get_tests(config={}):
+ tests = []
+ tests += [RNGForkTest()]
+ if multiprocessing is not None:
+ tests += [RNGMultiprocessingForkTest()]
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/test_random.py b/lib/Python/Lib/Crypto/SelfTest/Random/test_random.py
new file mode 100644
index 000000000..f9ffc663a
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/test_random.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_generic.py: Self-test for the Crypto.Random.new() function
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Random.new()"""
+
+__revision__ = "$Id$"
+
+import unittest
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+class SimpleTest(unittest.TestCase):
+ def runTest(self):
+ """Crypto.Random.new()"""
+ # Import the Random module and try to use it
+ from Crypto import Random
+ randobj = Random.new()
+ x = randobj.read(16)
+ y = randobj.read(16)
+ self.assertNotEqual(x, y)
+ z = Random.get_random_bytes(16)
+ self.assertNotEqual(x, z)
+ self.assertNotEqual(y, z)
+ # Test the Random.random module, which
+ # implements a subset of Python's random API
+ # Not implemented:
+ # seed(), getstate(), setstate(), jumpahead()
+ # random(), uniform(), triangular(), betavariate()
+ # expovariate(), gammavariate(), gauss(),
+ # longnormvariate(), normalvariate(),
+ # vonmisesvariate(), paretovariate()
+ # weibullvariate()
+ # WichmannHill(), whseed(), SystemRandom()
+ from Crypto.Random import random
+ x = random.getrandbits(16*8)
+ y = random.getrandbits(16*8)
+ self.assertNotEqual(x, y)
+ # Test randrange
+ if x>y:
+ start = y
+ stop = x
+ else:
+ start = x
+ stop = y
+ for step in range(1,10):
+ x = random.randrange(start,stop,step)
+ y = random.randrange(start,stop,step)
+ self.assertNotEqual(x, y)
+ self.assertEqual(start <= x < stop, True)
+ self.assertEqual(start <= y < stop, True)
+ self.assertEqual((x - start) % step, 0)
+ self.assertEqual((y - start) % step, 0)
+ for i in range(10):
+ self.assertEqual(random.randrange(1,2), 1)
+ self.assertRaises(ValueError, random.randrange, start, start)
+ self.assertRaises(ValueError, random.randrange, stop, start, step)
+ self.assertRaises(TypeError, random.randrange, start, stop, step, step)
+ self.assertRaises(TypeError, random.randrange, start, stop, "1")
+ self.assertRaises(TypeError, random.randrange, "1", stop, step)
+ self.assertRaises(TypeError, random.randrange, 1, "2", step)
+ self.assertRaises(ValueError, random.randrange, start, stop, 0)
+ # Test randint
+ x = random.randint(start,stop)
+ y = random.randint(start,stop)
+ self.assertNotEqual(x, y)
+ self.assertEqual(start <= x <= stop, True)
+ self.assertEqual(start <= y <= stop, True)
+ for i in range(10):
+ self.assertEqual(random.randint(1,1), 1)
+ self.assertRaises(ValueError, random.randint, stop, start)
+ self.assertRaises(TypeError, random.randint, start, stop, step)
+ self.assertRaises(TypeError, random.randint, "1", stop)
+ self.assertRaises(TypeError, random.randint, 1, "2")
+ # Test choice
+ seq = range(10000)
+ x = random.choice(seq)
+ y = random.choice(seq)
+ self.assertNotEqual(x, y)
+ self.assertEqual(x in seq, True)
+ self.assertEqual(y in seq, True)
+ for i in range(10):
+ self.assertEqual(random.choice((1,2,3)) in (1,2,3), True)
+ self.assertEqual(random.choice([1,2,3]) in [1,2,3], True)
+ if sys.version_info[0] is 3:
+ self.assertEqual(random.choice(bytearray(b('123'))) in bytearray(b('123')), True)
+ self.assertEqual(1, random.choice([1]))
+ self.assertRaises(IndexError, random.choice, [])
+ self.assertRaises(TypeError, random.choice, 1)
+ # Test shuffle. Lacks random parameter to specify function.
+ # Make copies of seq
+ seq = range(500)
+ x = list(seq)
+ y = list(seq)
+ random.shuffle(x)
+ random.shuffle(y)
+ self.assertNotEqual(x, y)
+ self.assertEqual(len(seq), len(x))
+ self.assertEqual(len(seq), len(y))
+ for i in range(len(seq)):
+ self.assertEqual(x[i] in seq, True)
+ self.assertEqual(y[i] in seq, True)
+ self.assertEqual(seq[i] in x, True)
+ self.assertEqual(seq[i] in y, True)
+ z = [1]
+ random.shuffle(z)
+ self.assertEqual(z, [1])
+ if sys.version_info[0] == 3:
+ z = bytearray(b('12'))
+ random.shuffle(z)
+ self.assertEqual(b('1') in z, True)
+ self.assertRaises(TypeError, random.shuffle, b('12'))
+ self.assertRaises(TypeError, random.shuffle, 1)
+ self.assertRaises(TypeError, random.shuffle, "1")
+ self.assertRaises(TypeError, random.shuffle, (1,2))
+ # 2to3 wraps a list() around it, alas - but I want to shoot
+ # myself in the foot here! :D
+ # if sys.version_info[0] == 3:
+ # self.assertRaises(TypeError, random.shuffle, range(3))
+ # Test sample
+ x = random.sample(seq, 20)
+ y = random.sample(seq, 20)
+ self.assertNotEqual(x, y)
+ for i in range(20):
+ self.assertEqual(x[i] in seq, True)
+ self.assertEqual(y[i] in seq, True)
+ z = random.sample([1], 1)
+ self.assertEqual(z, [1])
+ z = random.sample((1,2,3), 1)
+ self.assertEqual(z[0] in (1,2,3), True)
+ z = random.sample("123", 1)
+ self.assertEqual(z[0] in "123", True)
+ z = random.sample(range(3), 1)
+ self.assertEqual(z[0] in range(3), True)
+ if sys.version_info[0] == 3:
+ z = random.sample(b("123"), 1)
+ self.assertEqual(z[0] in b("123"), True)
+ z = random.sample(bytearray(b("123")), 1)
+ self.assertEqual(z[0] in bytearray(b("123")), True)
+ self.assertRaises(TypeError, random.sample, 1)
+
+def get_tests(config={}):
+ return [SimpleTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Random/test_rpoolcompat.py b/lib/Python/Lib/Crypto/SelfTest/Random/test_rpoolcompat.py
new file mode 100644
index 000000000..be538da49
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Random/test_rpoolcompat.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_winrandom.py: Self-test for the winrandom module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for the Crypto.Util.randpool.RandomPool wrapper class"""
+
+__revision__ = "$Id$"
+
+import sys
+import unittest
+
+class SimpleTest(unittest.TestCase):
+ def runTest(self):
+ """Crypto.Util.randpool.RandomPool"""
+ # Import the winrandom module and try to use it
+ from Crypto.Util.randpool import RandomPool
+ sys.stderr.write("SelfTest: You can ignore the RandomPool_DeprecationWarning that follows.\n")
+ rpool = RandomPool()
+ x = rpool.get_bytes(16)
+ y = rpool.get_bytes(16)
+ self.assertNotEqual(x, y)
+ self.assertNotEqual(rpool.entropy, 0)
+
+ rpool.randomize()
+ rpool.stir('foo')
+ rpool.add_event('foo')
+
+def get_tests(config={}):
+ return [SimpleTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Signature/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Signature/__init__.py
new file mode 100644
index 000000000..653b66ca2
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Signature/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Signature/__init__.py: Self-test for signature modules
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for signature modules"""
+
+__revision__ = "$Id$"
+
+import os
+
+def get_tests(config={}):
+ tests = []
+ import test_pkcs1_15; tests += test_pkcs1_15.get_tests(config=config)
+ import test_pkcs1_pss; tests += test_pkcs1_pss.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_15.py b/lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_15.py
new file mode 100644
index 000000000..bc3669626
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_15.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Signature/test_pkcs1_15.py: Self-test for PKCS#1 v1.5 signatures
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+import unittest
+
+from Crypto.PublicKey import RSA
+from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex
+from Crypto.Hash import *
+from Crypto import Random
+from Crypto.Signature import PKCS1_v1_5 as PKCS
+from Crypto.Util.py3compat import *
+
+def isStr(s):
+ t = ''
+ try:
+ t += s
+ except TypeError:
+ return 0
+ return 1
+
+def rws(t):
+ """Remove white spaces, tabs, and new lines from a string"""
+ for c in ['\n', '\t', ' ']:
+ t = t.replace(c,'')
+ return t
+
+def t2b(t):
+ """Convert a text string with bytes in hex form to a byte string"""
+ clean = b(rws(t))
+ if len(clean)%2 == 1:
+ raise ValueError("Even number of characters expected")
+ return a2b_hex(clean)
+
+class PKCS1_15_Tests(unittest.TestCase):
+
+ # List of tuples with test data for PKCS#1 v1.5.
+ # Each tuple is made up by:
+ # Item #0: dictionary with RSA key component, or key to import
+ # Item #1: data to hash and sign
+ # Item #2: signature of the data #1, done with the key #0, after
+ # hashing it with #3
+ # Item #3: hash object generator
+
+ _testData = (
+
+ #
+ # Taken from ftp://ftp.rsa.com/pub/pkcs/ascii/examples.asc
+ # "Some Examples of the PKCS Standards", 1999
+ #
+ (
+
+ # Private key, from 2.1
+ {
+ 'n':'''0a 66 79 1d c6 98 81 68 de 7a b7 74 19 bb 7f b0 c0 01 c6
+ 27 10 27 00 75 14 29 42 e1 9a 8d 8c 51 d0 53 b3 e3 78 2a 1d
+ e5 dc 5a f4 eb e9 94 68 17 01 14 a1 df e6 7c dc 9a 9a f5 5d
+ 65 56 20 bb ab''',
+ 'e':'''01 00
+ 01''',
+ 'd':'''01 23 c5 b6 1b a3 6e db 1d 36 79 90 41 99 a8 9e a8 0c 09
+ b9 12 2e 14 00 c0 9a dc f7 78 46 76 d0 1d 23 35 6a 7d 44 d6
+ bd 8b d5 0e 94 bf c7 23 fa 87 d8 86 2b 75 17 76 91 c1 1d 75
+ 76 92 df 88 81'''
+ },
+ # Data to sign, from 3.1
+ '''30 81 a4 02 01 00 30 42 31 0b 30 09 06
+ 03 55 04 06 13 02 55 53 31 1d 30 1b 06 03 55 04 0a 13 14
+ 45 78 61 6d 70 6c 65 20 4f 72 67 61 6e 69 7a 61 74 69 6f
+ 6e 31 14 30 12 06 03 55 04 03 13 0b 54 65 73 74 20 55 73
+ 65 72 20 31 30 5b 30 0d 06 09 2a 86 48 86 f7 0d 01 01 01
+ 05 00 03 4a 00 30 47 02 40
+ 0a 66 79 1d c6 98 81 68 de 7a b7 74 19 bb 7f b0
+ c0 01 c6 27 10 27 00 75 14 29 42 e1 9a 8d 8c 51
+ d0 53 b3 e3 78 2a 1d e5 dc 5a f4 eb e9 94 68 17
+ 01 14 a1 df e6 7c dc 9a 9a f5 5d 65 56 20 bb ab
+ 02 03 01 00 01''',
+ # Signature, from 3.2 (at the very end)
+ '''06 db 36 cb 18 d3 47 5b 9c 01 db 3c 78 95 28 08
+ 02 79 bb ae ff 2b 7d 55 8e d6 61 59 87 c8 51 86
+ 3f 8a 6c 2c ff bc 89 c3 f7 5a 18 d9 6b 12 7c 71
+ 7d 54 d0 d8 04 8d a8 a0 54 46 26 d1 7a 2a 8f be''',
+ MD2
+ ),
+
+ #
+ # RSA keypair generated with openssl
+ #
+ (
+ """-----BEGIN RSA PRIVATE KEY-----
+ MIIBOwIBAAJBAL8eJ5AKoIsjURpcEoGubZMxLD7+kT+TLr7UkvEtFrRhDDKMtuII
+ q19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQJACUSDEp8RTe32ftq8IwG8
+ Wojl5mAd1wFiIOrZ/Uv8b963WJOJiuQcVN29vxU5+My9GPZ7RA3hrDBEAoHUDPrI
+ OQIhAPIPLz4dphiD9imAkivY31Rc5AfHJiQRA7XixTcjEkojAiEAyh/pJHks/Mlr
+ +rdPNEpotBjfV4M4BkgGAA/ipcmaAjcCIQCHvhwwKVBLzzTscT2HeUdEeBMoiXXK
+ JACAr3sJQJGxIQIgarRp+m1WSKV1MciwMaTOnbU7wxFs9DP1pva76lYBzgUCIQC9
+ n0CnZCJ6IZYqSt0H5N7+Q+2Ro64nuwV/OSQfM6sBwQ==
+ -----END RSA PRIVATE KEY-----""",
+ "This is a test\x0a",
+ #
+ # PKCS#1 signature computed with openssl
+ #
+ '''4a700a16432a291a3194646952687d5316458b8b86fb0a25aa30e0dcecdb
+ 442676759ac63d56ec1499c3ae4c0013c2053cabd5b5804848994541ac16
+ fa243a4d''',
+ SHA
+ ),
+
+ #
+ # Test vector from http://www.di-mgt.com.au/rsa_alg.html#signpkcs1
+ #
+ (
+ {
+ 'n':'''E08973398DD8F5F5E88776397F4EB005BB5383DE0FB7ABDC7DC775290D052E6D
+ 12DFA68626D4D26FAA5829FC97ECFA82510F3080BEB1509E4644F12CBBD832CF
+ C6686F07D9B060ACBEEE34096A13F5F7050593DF5EBA3556D961FF197FC981E6
+ F86CEA874070EFAC6D2C749F2DFA553AB9997702A648528C4EF357385774575F''',
+ 'e':'''010001''',
+ 'd':'''00A403C327477634346CA686B57949014B2E8AD2C862B2C7D748096A8B91F736
+ F275D6E8CD15906027314735644D95CD6763CEB49F56AC2F376E1CEE0EBF282D
+ F439906F34D86E085BD5656AD841F313D72D395EFE33CBFF29E4030B3D05A28F
+ B7F18EA27637B07957D32F2BDE8706227D04665EC91BAF8B1AC3EC9144AB7F21'''
+ },
+ "abc",
+ '''60AD5A78FB4A4030EC542C8974CD15F55384E836554CEDD9A322D5F4135C6267
+ A9D20970C54E6651070B0144D43844C899320DD8FA7819F7EBC6A7715287332E
+ C8675C136183B3F8A1F81EF969418267130A756FDBB2C71D9A667446E34E0EAD
+ 9CF31BFB66F816F319D0B7E430A5F2891553986E003720261C7E9022C0D9F11F''',
+ SHA
+ )
+
+ )
+
+ def testSign1(self):
+ for i in range(len(self._testData)):
+ row = self._testData[i]
+ # Build the key
+ if isStr(row[0]):
+ key = RSA.importKey(row[0])
+ else:
+ comps = [ long(rws(row[0][x]),16) for x in ('n','e','d') ]
+ key = RSA.construct(comps)
+ h = row[3].new()
+ # Data to sign can either be in hex form or not
+ try:
+ h.update(t2b(row[1]))
+ except:
+ h.update(b(row[1]))
+ # The real test
+ signer = PKCS.new(key)
+ self.failUnless(signer.can_sign())
+ s = signer.sign(h)
+ self.assertEqual(s, t2b(row[2]))
+
+ def testVerify1(self):
+ for i in range(len(self._testData)):
+ row = self._testData[i]
+ # Build the key
+ if isStr(row[0]):
+ key = RSA.importKey(row[0]).publickey()
+ else:
+ comps = [ long(rws(row[0][x]),16) for x in ('n','e') ]
+ key = RSA.construct(comps)
+ h = row[3].new()
+ # Data to sign can either be in hex form or not
+ try:
+ h.update(t2b(row[1]))
+ except:
+ h.update(b(row[1]))
+ # The real test
+ verifier = PKCS.new(key)
+ self.failIf(verifier.can_sign())
+ result = verifier.verify(h, t2b(row[2]))
+ self.failUnless(result)
+
+ def testSignVerify(self):
+ rng = Random.new().read
+ key = RSA.generate(1024, rng)
+
+ for hashmod in (MD2,MD5,SHA,SHA224,SHA256,SHA384,SHA512,RIPEMD):
+ h = hashmod.new()
+ h.update(b('blah blah blah'))
+
+ signer = PKCS.new(key)
+ s = signer.sign(h)
+ result = signer.verify(h, s)
+ self.failUnless(result)
+
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(PKCS1_15_Tests)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py b/lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py
new file mode 100644
index 000000000..f5256a512
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py
@@ -0,0 +1,446 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Signature/test_pkcs1_pss.py: Self-test for PKCS#1 PSS signatures
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+from __future__ import nested_scopes
+
+__revision__ = "$Id$"
+
+import unittest
+
+from Crypto.PublicKey import RSA
+from Crypto import Random
+from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex
+from Crypto.Hash import *
+from Crypto.Signature import PKCS1_PSS as PKCS
+from Crypto.Util.py3compat import *
+
+def isStr(s):
+ t = ''
+ try:
+ t += s
+ except TypeError:
+ return 0
+ return 1
+
+def rws(t):
+ """Remove white spaces, tabs, and new lines from a string"""
+ for c in ['\t', '\n', ' ']:
+ t = t.replace(c,'')
+ return t
+
+def t2b(t):
+ """Convert a text string with bytes in hex form to a byte string"""
+ clean = b(rws(t))
+ if len(clean)%2 == 1:
+ raise ValueError("Even number of characters expected")
+ return a2b_hex(clean)
+
+# Helper class to count how many bytes have been requested
+# from the key's private RNG, w/o counting those used for blinding
+class MyKey:
+ def __init__(self, key):
+ self._key = key
+ self.n = key.n
+ self.asked = 0
+ def _randfunc(self, N):
+ self.asked += N
+ return self._key._randfunc(N)
+ def sign(self, m):
+ return self._key.sign(m)
+ def has_private(self):
+ return self._key.has_private()
+ def decrypt(self, m):
+ return self._key.decrypt(m)
+ def verify(self, m, p):
+ return self._key.verify(m, p)
+ def encrypt(self, m, p):
+ return self._key.encrypt(m, p)
+
+class PKCS1_PSS_Tests(unittest.TestCase):
+
+ # List of tuples with test data for PKCS#1 PSS
+ # Each tuple is made up by:
+ # Item #0: dictionary with RSA key component, or key to import
+ # Item #1: data to hash and sign
+ # Item #2: signature of the data #1, done with the key #0,
+ # and salt #3 after hashing it with #4
+ # Item #3: salt
+ # Item #4: hash object generator
+
+ _testData = (
+
+ #
+ # From in pss-vect.txt to be found in
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''a2 ba 40 ee 07 e3 b2 bd 2f 02 ce 22 7f 36 a1 95
+ 02 44 86 e4 9c 19 cb 41 bb bd fb ba 98 b2 2b 0e
+ 57 7c 2e ea ff a2 0d 88 3a 76 e6 5e 39 4c 69 d4
+ b3 c0 5a 1e 8f ad da 27 ed b2 a4 2b c0 00 fe 88
+ 8b 9b 32 c2 2d 15 ad d0 cd 76 b3 e7 93 6e 19 95
+ 5b 22 0d d1 7d 4e a9 04 b1 ec 10 2b 2e 4d e7 75
+ 12 22 aa 99 15 10 24 c7 cb 41 cc 5e a2 1d 00 ee
+ b4 1f 7c 80 08 34 d2 c6 e0 6b ce 3b ce 7e a9 a5''',
+ 'e':'''01 00 01''',
+ # In the test vector, only p and q were given...
+ # d is computed offline as e^{-1} mod (p-1)(q-1)
+ 'd':'''50e2c3e38d886110288dfc68a9533e7e12e27d2aa56
+ d2cdb3fb6efa990bcff29e1d2987fb711962860e7391b1ce01
+ ebadb9e812d2fbdfaf25df4ae26110a6d7a26f0b810f54875e
+ 17dd5c9fb6d641761245b81e79f8c88f0e55a6dcd5f133abd3
+ 5f8f4ec80adf1bf86277a582894cb6ebcd2162f1c7534f1f49
+ 47b129151b71'''
+ },
+
+ # Data to sign
+ '''85 9e ef 2f d7 8a ca 00 30 8b dc 47 11 93 bf 55
+ bf 9d 78 db 8f 8a 67 2b 48 46 34 f3 c9 c2 6e 64
+ 78 ae 10 26 0f e0 dd 8c 08 2e 53 a5 29 3a f2 17
+ 3c d5 0c 6d 5d 35 4f eb f7 8b 26 02 1c 25 c0 27
+ 12 e7 8c d4 69 4c 9f 46 97 77 e4 51 e7 f8 e9 e0
+ 4c d3 73 9c 6b bf ed ae 48 7f b5 56 44 e9 ca 74
+ ff 77 a5 3c b7 29 80 2f 6e d4 a5 ff a8 ba 15 98
+ 90 fc''',
+ # Signature
+ '''8d aa 62 7d 3d e7 59 5d 63 05 6c 7e c6 59 e5 44
+ 06 f1 06 10 12 8b aa e8 21 c8 b2 a0 f3 93 6d 54
+ dc 3b dc e4 66 89 f6 b7 95 1b b1 8e 84 05 42 76
+ 97 18 d5 71 5d 21 0d 85 ef bb 59 61 92 03 2c 42
+ be 4c 29 97 2c 85 62 75 eb 6d 5a 45 f0 5f 51 87
+ 6f c6 74 3d ed dd 28 ca ec 9b b3 0e a9 9e 02 c3
+ 48 82 69 60 4f e4 97 f7 4c cd 7c 7f ca 16 71 89
+ 71 23 cb d3 0d ef 5d 54 a2 b5 53 6a d9 0a 74 7e''',
+ # Salt
+ '''e3 b5 d5 d0 02 c1 bc e5 0c 2b 65 ef 88 a1 88 d8
+ 3b ce 7e 61''',
+ # Hash algorithm
+ SHA
+ ),
+
+ #
+ # Example 1.1 to be found in
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''a5 6e 4a 0e 70 10 17 58 9a 51 87 dc 7e a8 41 d1
+ 56 f2 ec 0e 36 ad 52 a4 4d fe b1 e6 1f 7a d9 91
+ d8 c5 10 56 ff ed b1 62 b4 c0 f2 83 a1 2a 88 a3
+ 94 df f5 26 ab 72 91 cb b3 07 ce ab fc e0 b1 df
+ d5 cd 95 08 09 6d 5b 2b 8b 6d f5 d6 71 ef 63 77
+ c0 92 1c b2 3c 27 0a 70 e2 59 8e 6f f8 9d 19 f1
+ 05 ac c2 d3 f0 cb 35 f2 92 80 e1 38 6b 6f 64 c4
+ ef 22 e1 e1 f2 0d 0c e8 cf fb 22 49 bd 9a 21 37''',
+ 'e':'''01 00 01''',
+ 'd':'''33 a5 04 2a 90 b2 7d 4f 54 51 ca 9b bb d0 b4 47
+ 71 a1 01 af 88 43 40 ae f9 88 5f 2a 4b be 92 e8
+ 94 a7 24 ac 3c 56 8c 8f 97 85 3a d0 7c 02 66 c8
+ c6 a3 ca 09 29 f1 e8 f1 12 31 88 44 29 fc 4d 9a
+ e5 5f ee 89 6a 10 ce 70 7c 3e d7 e7 34 e4 47 27
+ a3 95 74 50 1a 53 26 83 10 9c 2a ba ca ba 28 3c
+ 31 b4 bd 2f 53 c3 ee 37 e3 52 ce e3 4f 9e 50 3b
+ d8 0c 06 22 ad 79 c6 dc ee 88 35 47 c6 a3 b3 25'''
+ },
+ # Message
+ '''cd c8 7d a2 23 d7 86 df 3b 45 e0 bb bc 72 13 26
+ d1 ee 2a f8 06 cc 31 54 75 cc 6f 0d 9c 66 e1 b6
+ 23 71 d4 5c e2 39 2e 1a c9 28 44 c3 10 10 2f 15
+ 6a 0d 8d 52 c1 f4 c4 0b a3 aa 65 09 57 86 cb 76
+ 97 57 a6 56 3b a9 58 fe d0 bc c9 84 e8 b5 17 a3
+ d5 f5 15 b2 3b 8a 41 e7 4a a8 67 69 3f 90 df b0
+ 61 a6 e8 6d fa ae e6 44 72 c0 0e 5f 20 94 57 29
+ cb eb e7 7f 06 ce 78 e0 8f 40 98 fb a4 1f 9d 61
+ 93 c0 31 7e 8b 60 d4 b6 08 4a cb 42 d2 9e 38 08
+ a3 bc 37 2d 85 e3 31 17 0f cb f7 cc 72 d0 b7 1c
+ 29 66 48 b3 a4 d1 0f 41 62 95 d0 80 7a a6 25 ca
+ b2 74 4f d9 ea 8f d2 23 c4 25 37 02 98 28 bd 16
+ be 02 54 6f 13 0f d2 e3 3b 93 6d 26 76 e0 8a ed
+ 1b 73 31 8b 75 0a 01 67 d0''',
+ # Signature
+ '''90 74 30 8f b5 98 e9 70 1b 22 94 38 8e 52 f9 71
+ fa ac 2b 60 a5 14 5a f1 85 df 52 87 b5 ed 28 87
+ e5 7c e7 fd 44 dc 86 34 e4 07 c8 e0 e4 36 0b c2
+ 26 f3 ec 22 7f 9d 9e 54 63 8e 8d 31 f5 05 12 15
+ df 6e bb 9c 2f 95 79 aa 77 59 8a 38 f9 14 b5 b9
+ c1 bd 83 c4 e2 f9 f3 82 a0 d0 aa 35 42 ff ee 65
+ 98 4a 60 1b c6 9e b2 8d eb 27 dc a1 2c 82 c2 d4
+ c3 f6 6c d5 00 f1 ff 2b 99 4d 8a 4e 30 cb b3 3c''',
+ # Salt
+ '''de e9 59 c7 e0 64 11 36 14 20 ff 80 18 5e d5 7f
+ 3e 67 76 af''',
+ # Hash
+ SHA
+ ),
+
+ #
+ # Example 1.2 to be found in
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''a5 6e 4a 0e 70 10 17 58 9a 51 87 dc 7e a8 41 d1
+ 56 f2 ec 0e 36 ad 52 a4 4d fe b1 e6 1f 7a d9 91
+ d8 c5 10 56 ff ed b1 62 b4 c0 f2 83 a1 2a 88 a3
+ 94 df f5 26 ab 72 91 cb b3 07 ce ab fc e0 b1 df
+ d5 cd 95 08 09 6d 5b 2b 8b 6d f5 d6 71 ef 63 77
+ c0 92 1c b2 3c 27 0a 70 e2 59 8e 6f f8 9d 19 f1
+ 05 ac c2 d3 f0 cb 35 f2 92 80 e1 38 6b 6f 64 c4
+ ef 22 e1 e1 f2 0d 0c e8 cf fb 22 49 bd 9a 21 37''',
+ 'e':'''01 00 01''',
+ 'd':'''33 a5 04 2a 90 b2 7d 4f 54 51 ca 9b bb d0 b4 47
+ 71 a1 01 af 88 43 40 ae f9 88 5f 2a 4b be 92 e8
+ 94 a7 24 ac 3c 56 8c 8f 97 85 3a d0 7c 02 66 c8
+ c6 a3 ca 09 29 f1 e8 f1 12 31 88 44 29 fc 4d 9a
+ e5 5f ee 89 6a 10 ce 70 7c 3e d7 e7 34 e4 47 27
+ a3 95 74 50 1a 53 26 83 10 9c 2a ba ca ba 28 3c
+ 31 b4 bd 2f 53 c3 ee 37 e3 52 ce e3 4f 9e 50 3b
+ d8 0c 06 22 ad 79 c6 dc ee 88 35 47 c6 a3 b3 25'''
+ },
+ # Message
+ '''85 13 84 cd fe 81 9c 22 ed 6c 4c cb 30 da eb 5c
+ f0 59 bc 8e 11 66 b7 e3 53 0c 4c 23 3e 2b 5f 8f
+ 71 a1 cc a5 82 d4 3e cc 72 b1 bc a1 6d fc 70 13
+ 22 6b 9e''',
+ # Signature
+ '''3e f7 f4 6e 83 1b f9 2b 32 27 41 42 a5 85 ff ce
+ fb dc a7 b3 2a e9 0d 10 fb 0f 0c 72 99 84 f0 4e
+ f2 9a 9d f0 78 07 75 ce 43 73 9b 97 83 83 90 db
+ 0a 55 05 e6 3d e9 27 02 8d 9d 29 b2 19 ca 2c 45
+ 17 83 25 58 a5 5d 69 4a 6d 25 b9 da b6 60 03 c4
+ cc cd 90 78 02 19 3b e5 17 0d 26 14 7d 37 b9 35
+ 90 24 1b e5 1c 25 05 5f 47 ef 62 75 2c fb e2 14
+ 18 fa fe 98 c2 2c 4d 4d 47 72 4f db 56 69 e8 43''',
+ # Salt
+ '''ef 28 69 fa 40 c3 46 cb 18 3d ab 3d 7b ff c9 8f
+ d5 6d f4 2d''',
+ # Hash
+ SHA
+ ),
+
+ #
+ # Example 2.1 to be found in
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''01 d4 0c 1b cf 97 a6 8a e7 cd bd 8a 7b f3 e3 4f
+ a1 9d cc a4 ef 75 a4 74 54 37 5f 94 51 4d 88 fe
+ d0 06 fb 82 9f 84 19 ff 87 d6 31 5d a6 8a 1f f3
+ a0 93 8e 9a bb 34 64 01 1c 30 3a d9 91 99 cf 0c
+ 7c 7a 8b 47 7d ce 82 9e 88 44 f6 25 b1 15 e5 e9
+ c4 a5 9c f8 f8 11 3b 68 34 33 6a 2f d2 68 9b 47
+ 2c bb 5e 5c ab e6 74 35 0c 59 b6 c1 7e 17 68 74
+ fb 42 f8 fc 3d 17 6a 01 7e dc 61 fd 32 6c 4b 33
+ c9''',
+ 'e':'''01 00 01''',
+ 'd':'''02 7d 14 7e 46 73 05 73 77 fd 1e a2 01 56 57 72
+ 17 6a 7d c3 83 58 d3 76 04 56 85 a2 e7 87 c2 3c
+ 15 57 6b c1 6b 9f 44 44 02 d6 bf c5 d9 8a 3e 88
+ ea 13 ef 67 c3 53 ec a0 c0 dd ba 92 55 bd 7b 8b
+ b5 0a 64 4a fd fd 1d d5 16 95 b2 52 d2 2e 73 18
+ d1 b6 68 7a 1c 10 ff 75 54 5f 3d b0 fe 60 2d 5f
+ 2b 7f 29 4e 36 01 ea b7 b9 d1 ce cd 76 7f 64 69
+ 2e 3e 53 6c a2 84 6c b0 c2 dd 48 6a 39 fa 75 b1'''
+ },
+ # Message
+ '''da ba 03 20 66 26 3f ae db 65 98 48 11 52 78 a5
+ 2c 44 fa a3 a7 6f 37 51 5e d3 36 32 10 72 c4 0a
+ 9d 9b 53 bc 05 01 40 78 ad f5 20 87 51 46 aa e7
+ 0f f0 60 22 6d cb 7b 1f 1f c2 7e 93 60''',
+ # Signature
+ '''01 4c 5b a5 33 83 28 cc c6 e7 a9 0b f1 c0 ab 3f
+ d6 06 ff 47 96 d3 c1 2e 4b 63 9e d9 13 6a 5f ec
+ 6c 16 d8 88 4b dd 99 cf dc 52 14 56 b0 74 2b 73
+ 68 68 cf 90 de 09 9a db 8d 5f fd 1d ef f3 9b a4
+ 00 7a b7 46 ce fd b2 2d 7d f0 e2 25 f5 46 27 dc
+ 65 46 61 31 72 1b 90 af 44 53 63 a8 35 8b 9f 60
+ 76 42 f7 8f ab 0a b0 f4 3b 71 68 d6 4b ae 70 d8
+ 82 78 48 d8 ef 1e 42 1c 57 54 dd f4 2c 25 89 b5
+ b3''',
+ # Salt
+ '''57 bf 16 0b cb 02 bb 1d c7 28 0c f0 45 85 30 b7
+ d2 83 2f f7''',
+ SHA
+ ),
+
+ #
+ # Example 8.1 to be found in
+ # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip
+ #
+ (
+ # Private key
+ {
+ 'n':'''49 53 70 a1 fb 18 54 3c 16 d3 63 1e 31 63 25 5d
+ f6 2b e6 ee e8 90 d5 f2 55 09 e4 f7 78 a8 ea 6f
+ bb bc df 85 df f6 4e 0d 97 20 03 ab 36 81 fb ba
+ 6d d4 1f d5 41 82 9b 2e 58 2d e9 f2 a4 a4 e0 a2
+ d0 90 0b ef 47 53 db 3c ee 0e e0 6c 7d fa e8 b1
+ d5 3b 59 53 21 8f 9c ce ea 69 5b 08 66 8e de aa
+ dc ed 94 63 b1 d7 90 d5 eb f2 7e 91 15 b4 6c ad
+ 4d 9a 2b 8e fa b0 56 1b 08 10 34 47 39 ad a0 73
+ 3f''',
+ 'e':'''01 00 01''',
+ 'd':'''6c 66 ff e9 89 80 c3 8f cd ea b5 15 98 98 83 61
+ 65 f4 b4 b8 17 c4 f6 a8 d4 86 ee 4e a9 13 0f e9
+ b9 09 2b d1 36 d1 84 f9 5f 50 4a 60 7e ac 56 58
+ 46 d2 fd d6 59 7a 89 67 c7 39 6e f9 5a 6e ee bb
+ 45 78 a6 43 96 6d ca 4d 8e e3 de 84 2d e6 32 79
+ c6 18 15 9c 1a b5 4a 89 43 7b 6a 61 20 e4 93 0a
+ fb 52 a4 ba 6c ed 8a 49 47 ac 64 b3 0a 34 97 cb
+ e7 01 c2 d6 26 6d 51 72 19 ad 0e c6 d3 47 db e9'''
+ },
+ # Message
+ '''81 33 2f 4b e6 29 48 41 5e a1 d8 99 79 2e ea cf
+ 6c 6e 1d b1 da 8b e1 3b 5c ea 41 db 2f ed 46 70
+ 92 e1 ff 39 89 14 c7 14 25 97 75 f5 95 f8 54 7f
+ 73 56 92 a5 75 e6 92 3a f7 8f 22 c6 99 7d db 90
+ fb 6f 72 d7 bb 0d d5 74 4a 31 de cd 3d c3 68 58
+ 49 83 6e d3 4a ec 59 63 04 ad 11 84 3c 4f 88 48
+ 9f 20 97 35 f5 fb 7f da f7 ce c8 ad dc 58 18 16
+ 8f 88 0a cb f4 90 d5 10 05 b7 a8 e8 4e 43 e5 42
+ 87 97 75 71 dd 99 ee a4 b1 61 eb 2d f1 f5 10 8f
+ 12 a4 14 2a 83 32 2e db 05 a7 54 87 a3 43 5c 9a
+ 78 ce 53 ed 93 bc 55 08 57 d7 a9 fb''',
+ # Signature
+ '''02 62 ac 25 4b fa 77 f3 c1 ac a2 2c 51 79 f8 f0
+ 40 42 2b 3c 5b af d4 0a 8f 21 cf 0f a5 a6 67 cc
+ d5 99 3d 42 db af b4 09 c5 20 e2 5f ce 2b 1e e1
+ e7 16 57 7f 1e fa 17 f3 da 28 05 2f 40 f0 41 9b
+ 23 10 6d 78 45 aa f0 11 25 b6 98 e7 a4 df e9 2d
+ 39 67 bb 00 c4 d0 d3 5b a3 55 2a b9 a8 b3 ee f0
+ 7c 7f ec db c5 42 4a c4 db 1e 20 cb 37 d0 b2 74
+ 47 69 94 0e a9 07 e1 7f bb ca 67 3b 20 52 23 80
+ c5''',
+ # Salt
+ '''1d 65 49 1d 79 c8 64 b3 73 00 9b e6 f6 f2 46 7b
+ ac 4c 78 fa''',
+ SHA
+ )
+ )
+
+ def testSign1(self):
+ for i in range(len(self._testData)):
+ # Build the key
+ comps = [ long(rws(self._testData[i][0][x]),16) for x in ('n','e','d') ]
+ key = MyKey(RSA.construct(comps))
+ # Hash function
+ h = self._testData[i][4].new()
+ # Data to sign
+ h.update(t2b(self._testData[i][1]))
+ # Salt
+ test_salt = t2b(self._testData[i][3])
+ key._randfunc = lambda N: test_salt
+ # The real test
+ signer = PKCS.new(key)
+ self.failUnless(signer.can_sign())
+ s = signer.sign(h)
+ self.assertEqual(s, t2b(self._testData[i][2]))
+
+ def testVerify1(self):
+ for i in range(len(self._testData)):
+ # Build the key
+ comps = [ long(rws(self._testData[i][0][x]),16) for x in ('n','e') ]
+ key = MyKey(RSA.construct(comps))
+ # Hash function
+ h = self._testData[i][4].new()
+ # Data to sign
+ h.update(t2b(self._testData[i][1]))
+ # Salt
+ test_salt = t2b(self._testData[i][3])
+ # The real test
+ key._randfunc = lambda N: test_salt
+ verifier = PKCS.new(key)
+ self.failIf(verifier.can_sign())
+ result = verifier.verify(h, t2b(self._testData[i][2]))
+ self.failUnless(result)
+
+ def testSignVerify(self):
+ h = SHA.new()
+ h.update(b('blah blah blah'))
+
+ rng = Random.new().read
+ key = MyKey(RSA.generate(1024,rng))
+
+ # Helper function to monitor what's request from MGF
+ global mgfcalls
+ def newMGF(seed,maskLen):
+ global mgfcalls
+ mgfcalls += 1
+ return bchr(0x00)*maskLen
+
+ # Verify that PSS is friendly to all ciphers
+ for hashmod in (MD2,MD5,SHA,SHA224,SHA256,SHA384,RIPEMD):
+ h = hashmod.new()
+ h.update(b('blah blah blah'))
+
+ # Verify that sign() asks for as many random bytes
+ # as the hash output size
+ key.asked = 0
+ signer = PKCS.new(key)
+ s = signer.sign(h)
+ self.failUnless(signer.verify(h, s))
+ self.assertEqual(key.asked, h.digest_size)
+
+ h = SHA.new()
+ h.update(b('blah blah blah'))
+
+ # Verify that sign() uses a different salt length
+ for sLen in (0,3,21):
+ key.asked = 0
+ signer = PKCS.new(key, saltLen=sLen)
+ s = signer.sign(h)
+ self.assertEqual(key.asked, sLen)
+ self.failUnless(signer.verify(h, s))
+
+ # Verify that sign() uses the custom MGF
+ mgfcalls = 0
+ signer = PKCS.new(key, newMGF)
+ s = signer.sign(h)
+ self.assertEqual(mgfcalls, 1)
+ self.failUnless(signer.verify(h, s))
+
+ # Verify that sign() does not call the RNG
+ # when salt length is 0, even when a new MGF is provided
+ key.asked = 0
+ mgfcalls = 0
+ signer = PKCS.new(key, newMGF, 0)
+ s = signer.sign(h)
+ self.assertEqual(key.asked,0)
+ self.assertEqual(mgfcalls, 1)
+ self.failUnless(signer.verify(h, s))
+
+def get_tests(config={}):
+ tests = []
+ tests += list_test_cases(PKCS1_PSS_Tests)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4
diff --git a/lib/Python/Lib/Crypto/SelfTest/Util/__init__.py b/lib/Python/Lib/Crypto/SelfTest/Util/__init__.py
new file mode 100644
index 000000000..abd640adf
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Util/__init__.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/__init__.py: Self-test for utility modules
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test for utility modules"""
+
+__revision__ = "$Id$"
+
+import os
+
+def get_tests(config={}):
+ tests = []
+ if os.name == 'nt':
+ from Crypto.SelfTest.Util import test_winrandom; tests += test_winrandom.get_tests(config=config)
+ from Crypto.SelfTest.Util import test_number; tests += test_number.get_tests(config=config)
+ from Crypto.SelfTest.Util import test_Counter; tests += test_Counter.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ import unittest
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Util/test_Counter.py b/lib/Python/Lib/Crypto/SelfTest/Util/test_Counter.py
new file mode 100644
index 000000000..33c9bd7d5
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Util/test_Counter.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_Counter: Self-test for the Crypto.Util.Counter module
+#
+# Written in 2009 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-tests for Crypto.Util.Counter"""
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+import unittest
+
+class CounterTests(unittest.TestCase):
+ def setUp(self):
+ global Counter
+ from Crypto.Util import Counter
+
+ def test_BE_shortcut(self):
+ """Big endian, shortcut enabled"""
+ c = Counter.new(128)
+ self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_
+ c = Counter.new(128, little_endian=False)
+ self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_
+ c = Counter.new(128, disable_shortcut=False)
+ self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_
+ c = Counter.new(128, little_endian=False, disable_shortcut=False)
+ self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_
+
+ def test_LE_shortcut(self):
+ """Little endian, shortcut enabled"""
+ c = Counter.new(128, little_endian=True)
+ self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_
+ c = Counter.new(128, little_endian=True, disable_shortcut=False)
+ self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_
+
+ def test_BE_no_shortcut(self):
+ """Big endian, shortcut disabled"""
+ c = Counter.new(128, disable_shortcut=True)
+ self.assertRaises(AttributeError, getattr, c, '__PCT_CTR_SHORTCUT__')
+ c = Counter.new(128, little_endian=False, disable_shortcut=True)
+ self.assertRaises(AttributeError, getattr, c, '__PCT_CTR_SHORTCUT__')
+
+ def test_LE_no_shortcut(self):
+ """Little endian, shortcut disabled"""
+ c = Counter.new(128, little_endian=True, disable_shortcut=True)
+ self.assertRaises(AttributeError, getattr, c, '__PCT_CTR_SHORTCUT__')
+
+ def test_BE_defaults(self):
+ """128-bit, Big endian, defaults"""
+ c = Counter.new(128)
+ self.assertEqual(1, c.next_value())
+ self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), c())
+ self.assertEqual(2, c.next_value())
+ self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"), c())
+ for i in xrange(3, 256):
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")+bchr(i), c())
+ self.assertEqual(256, c.next_value())
+ self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00"), c())
+
+ def test_LE_defaults(self):
+ """128-bit, Little endian, defaults"""
+ c = Counter.new(128, little_endian=True)
+ self.assertEqual(1, c.next_value())
+ self.assertEqual(b("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c())
+ self.assertEqual(2, c.next_value())
+ self.assertEqual(b("\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c())
+ for i in xrange(3, 256):
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(bchr(i)+b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c())
+ self.assertEqual(256, c.next_value())
+ self.assertEqual(b("\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c())
+
+ def test_BE8_wraparound(self):
+ """8-bit, Big endian, wraparound"""
+ c = Counter.new(8)
+ for i in xrange(1, 256):
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(bchr(i), c())
+ self.assertRaises(OverflowError, c.next_value)
+ self.assertRaises(OverflowError, c)
+ self.assertRaises(OverflowError, c.next_value)
+ self.assertRaises(OverflowError, c)
+
+ def test_LE8_wraparound(self):
+ """8-bit, Little endian, wraparound"""
+ c = Counter.new(8, little_endian=True)
+ for i in xrange(1, 256):
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(bchr(i), c())
+ self.assertRaises(OverflowError, c.next_value)
+ self.assertRaises(OverflowError, c)
+ self.assertRaises(OverflowError, c.next_value)
+ self.assertRaises(OverflowError, c)
+
+ def test_BE8_wraparound_allowed(self):
+ """8-bit, Big endian, wraparound with allow_wraparound=True"""
+ c = Counter.new(8, allow_wraparound=True)
+ for i in xrange(1, 256):
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(bchr(i), c())
+ self.assertEqual(0, c.next_value())
+ self.assertEqual(b("\x00"), c())
+ self.assertEqual(1, c.next_value())
+
+ def test_LE8_wraparound_allowed(self):
+ """8-bit, Little endian, wraparound with allow_wraparound=True"""
+ c = Counter.new(8, little_endian=True, allow_wraparound=True)
+ for i in xrange(1, 256):
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(bchr(i), c())
+ self.assertEqual(0, c.next_value())
+ self.assertEqual(b("\x00"), c())
+ self.assertEqual(1, c.next_value())
+
+ def test_BE8_carry(self):
+ """8-bit, Big endian, carry attribute"""
+ c = Counter.new(8)
+ for i in xrange(1, 256):
+ self.assertEqual(0, c.carry)
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(bchr(i), c())
+ self.assertEqual(1, c.carry)
+
+ def test_LE8_carry(self):
+ """8-bit, Little endian, carry attribute"""
+ c = Counter.new(8, little_endian=True)
+ for i in xrange(1, 256):
+ self.assertEqual(0, c.carry)
+ self.assertEqual(i, c.next_value())
+ self.assertEqual(bchr(i), c())
+ self.assertEqual(1, c.carry)
+
+def get_tests(config={}):
+ from Crypto.SelfTest.st_common import list_test_cases
+ return list_test_cases(CounterTests)
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Util/test_asn1.py b/lib/Python/Lib/Crypto/SelfTest/Util/test_asn1.py
new file mode 100644
index 000000000..578dabef8
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Util/test_asn1.py
@@ -0,0 +1,293 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_asn.py: Self-test for the Crypto.Util.asn1 module
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-tests for Crypto.Util.asn1"""
+
+__revision__ = "$Id$"
+
+import unittest
+import sys
+
+from Crypto.Util.py3compat import *
+from Crypto.Util.asn1 import DerSequence, DerObject
+
+class DerObjectTests(unittest.TestCase):
+
+ def testObjEncode1(self):
+ # No payload
+ der = DerObject(b('\x33'))
+ self.assertEquals(der.encode(), b('\x33\x00'))
+ # Small payload
+ der.payload = b('\x45')
+ self.assertEquals(der.encode(), b('\x33\x01\x45'))
+ # Invariant
+ self.assertEquals(der.encode(), b('\x33\x01\x45'))
+ # Initialize with numerical tag
+ der = DerObject(b(0x33))
+ der.payload = b('\x45')
+ self.assertEquals(der.encode(), b('\x33\x01\x45'))
+
+ def testObjEncode2(self):
+ # Known types
+ der = DerObject('SEQUENCE')
+ self.assertEquals(der.encode(), b('\x30\x00'))
+ der = DerObject('BIT STRING')
+ self.assertEquals(der.encode(), b('\x03\x00'))
+
+ def testObjEncode3(self):
+ # Long payload
+ der = DerObject(b('\x34'))
+ der.payload = b("0")*128
+ self.assertEquals(der.encode(), b('\x34\x81\x80' + "0"*128))
+
+ def testObjDecode1(self):
+ # Decode short payload
+ der = DerObject()
+ der.decode(b('\x20\x02\x01\x02'))
+ self.assertEquals(der.payload, b("\x01\x02"))
+ self.assertEquals(der.typeTag, 0x20)
+
+ def testObjDecode2(self):
+ # Decode short payload
+ der = DerObject()
+ der.decode(b('\x22\x81\x80' + "1"*128))
+ self.assertEquals(der.payload, b("1")*128)
+ self.assertEquals(der.typeTag, 0x22)
+
+class DerSequenceTests(unittest.TestCase):
+
+ def testEncode1(self):
+ # Empty sequence
+ der = DerSequence()
+ self.assertEquals(der.encode(), b('0\x00'))
+ self.failIf(der.hasOnlyInts())
+ # One single-byte integer (zero)
+ der.append(0)
+ self.assertEquals(der.encode(), b('0\x03\x02\x01\x00'))
+ self.failUnless(der.hasOnlyInts())
+ # Invariant
+ self.assertEquals(der.encode(), b('0\x03\x02\x01\x00'))
+
+ def testEncode2(self):
+ # One single-byte integer (non-zero)
+ der = DerSequence()
+ der.append(127)
+ self.assertEquals(der.encode(), b('0\x03\x02\x01\x7f'))
+ # Indexing
+ der[0] = 1
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],1)
+ self.assertEquals(der[-1],1)
+ self.assertEquals(der.encode(), b('0\x03\x02\x01\x01'))
+ #
+ der[:] = [1]
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],1)
+ self.assertEquals(der.encode(), b('0\x03\x02\x01\x01'))
+
+ def testEncode3(self):
+ # One multi-byte integer (non-zero)
+ der = DerSequence()
+ der.append(0x180L)
+ self.assertEquals(der.encode(), b('0\x04\x02\x02\x01\x80'))
+
+ def testEncode4(self):
+ # One very long integer
+ der = DerSequence()
+ der.append(2**2048)
+ self.assertEquals(der.encode(), b('0\x82\x01\x05')+
+ b('\x02\x82\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00'))
+
+ def testEncode5(self):
+ # One single-byte integer (looks negative)
+ der = DerSequence()
+ der.append(0xFFL)
+ self.assertEquals(der.encode(), b('0\x04\x02\x02\x00\xff'))
+
+ def testEncode6(self):
+ # Two integers
+ der = DerSequence()
+ der.append(0x180L)
+ der.append(0xFFL)
+ self.assertEquals(der.encode(), b('0\x08\x02\x02\x01\x80\x02\x02\x00\xff'))
+ self.failUnless(der.hasOnlyInts())
+ #
+ der.append(0x01)
+ der[1:] = [9,8]
+ self.assertEquals(len(der),3)
+ self.assertEqual(der[1:],[9,8])
+ self.assertEqual(der[1:-1],[9])
+ self.assertEquals(der.encode(), b('0\x0A\x02\x02\x01\x80\x02\x01\x09\x02\x01\x08'))
+
+ def testEncode6(self):
+ # One integer and another type (no matter what it is)
+ der = DerSequence()
+ der.append(0x180L)
+ der.append(b('\x00\x02\x00\x00'))
+ self.assertEquals(der.encode(), b('0\x08\x02\x02\x01\x80\x00\x02\x00\x00'))
+ self.failIf(der.hasOnlyInts())
+
+ ####
+
+ def testDecode1(self):
+ # Empty sequence
+ der = DerSequence()
+ der.decode(b('0\x00'))
+ self.assertEquals(len(der),0)
+ # One single-byte integer (zero)
+ der.decode(b('0\x03\x02\x01\x00'))
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],0)
+ # Invariant
+ der.decode(b('0\x03\x02\x01\x00'))
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],0)
+
+ def testDecode2(self):
+ # One single-byte integer (non-zero)
+ der = DerSequence()
+ der.decode(b('0\x03\x02\x01\x7f'))
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],127)
+
+ def testDecode3(self):
+ # One multi-byte integer (non-zero)
+ der = DerSequence()
+ der.decode(b('0\x04\x02\x02\x01\x80'))
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],0x180L)
+
+ def testDecode4(self):
+ # One very long integer
+ der = DerSequence()
+ der.decode(b('0\x82\x01\x05')+
+ b('\x02\x82\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+
+ b('\x00\x00\x00\x00\x00\x00\x00\x00\x00'))
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],2**2048)
+
+ def testDecode5(self):
+ # One single-byte integer (looks negative)
+ der = DerSequence()
+ der.decode(b('0\x04\x02\x02\x00\xff'))
+ self.assertEquals(len(der),1)
+ self.assertEquals(der[0],0xFFL)
+
+ def testDecode6(self):
+ # Two integers
+ der = DerSequence()
+ der.decode(b('0\x08\x02\x02\x01\x80\x02\x02\x00\xff'))
+ self.assertEquals(len(der),2)
+ self.assertEquals(der[0],0x180L)
+ self.assertEquals(der[1],0xFFL)
+
+ def testDecode7(self):
+ # One integer and 2 other types
+ der = DerSequence()
+ der.decode(b('0\x0A\x02\x02\x01\x80\x24\x02\xb6\x63\x12\x00'))
+ self.assertEquals(len(der),3)
+ self.assertEquals(der[0],0x180L)
+ self.assertEquals(der[1],b('\x24\x02\xb6\x63'))
+ self.assertEquals(der[2],b('\x12\x00'))
+
+ def testDecode8(self):
+ # Only 2 other types
+ der = DerSequence()
+ der.decode(b('0\x06\x24\x02\xb6\x63\x12\x00'))
+ self.assertEquals(len(der),2)
+ self.assertEquals(der[0],b('\x24\x02\xb6\x63'))
+ self.assertEquals(der[1],b('\x12\x00'))
+
+ def testErrDecode1(self):
+ # Not a sequence
+ der = DerSequence()
+ self.assertRaises(ValueError, der.decode, b(''))
+ self.assertRaises(ValueError, der.decode, b('\x00'))
+ self.assertRaises(ValueError, der.decode, b('\x30'))
+
+ def testErrDecode2(self):
+ # Wrong payload type
+ der = DerSequence()
+ self.assertRaises(ValueError, der.decode, b('\x30\x00\x00'), True)
+
+ def testErrDecode3(self):
+ # Wrong length format
+ der = DerSequence()
+ self.assertRaises(ValueError, der.decode, b('\x30\x04\x02\x01\x01\x00'))
+ self.assertRaises(ValueError, der.decode, b('\x30\x81\x03\x02\x01\x01'))
+ self.assertRaises(ValueError, der.decode, b('\x30\x04\x02\x81\x01\x01'))
+
+ def testErrDecode4(self):
+ # Wrong integer format
+ der = DerSequence()
+ # Multi-byte encoding for zero
+ #self.assertRaises(ValueError, der.decode, '\x30\x04\x02\x02\x00\x00')
+ # Negative integer
+ self.assertRaises(ValueError, der.decode, b('\x30\x04\x02\x01\xFF'))
+
+def get_tests(config={}):
+ from Crypto.SelfTest.st_common import list_test_cases
+ listTests = []
+ listTests += list_test_cases(DerObjectTests)
+ listTests += list_test_cases(DerSequenceTests)
+ return listTests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Util/test_number.py b/lib/Python/Lib/Crypto/SelfTest/Util/test_number.py
new file mode 100644
index 000000000..0502e9e18
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Util/test_number.py
@@ -0,0 +1,295 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_number.py: Self-test for parts of the Crypto.Util.number module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-tests for (some of) Crypto.Util.number"""
+
+__revision__ = "$Id$"
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+
+import unittest
+
+# NB: In some places, we compare tuples instead of just output values so that
+# if any inputs cause a test failure, we'll be able to tell which ones.
+
+class MiscTests(unittest.TestCase):
+ def setUp(self):
+ global number, math
+ from Crypto.Util import number
+ import math
+
+ def test_ceil_shift(self):
+ """Util.number.ceil_shift"""
+ self.assertRaises(AssertionError, number.ceil_shift, -1, 1)
+ self.assertRaises(AssertionError, number.ceil_shift, 1, -1)
+
+ # b = 0
+ self.assertEqual(0, number.ceil_shift(0, 0))
+ self.assertEqual(1, number.ceil_shift(1, 0))
+ self.assertEqual(2, number.ceil_shift(2, 0))
+ self.assertEqual(3, number.ceil_shift(3, 0))
+
+ # b = 1
+ self.assertEqual(0, number.ceil_shift(0, 1))
+ self.assertEqual(1, number.ceil_shift(1, 1))
+ self.assertEqual(1, number.ceil_shift(2, 1))
+ self.assertEqual(2, number.ceil_shift(3, 1))
+
+ # b = 2
+ self.assertEqual(0, number.ceil_shift(0, 2))
+ self.assertEqual(1, number.ceil_shift(1, 2))
+ self.assertEqual(1, number.ceil_shift(2, 2))
+ self.assertEqual(1, number.ceil_shift(3, 2))
+ self.assertEqual(1, number.ceil_shift(4, 2))
+ self.assertEqual(2, number.ceil_shift(5, 2))
+ self.assertEqual(2, number.ceil_shift(6, 2))
+ self.assertEqual(2, number.ceil_shift(7, 2))
+ self.assertEqual(2, number.ceil_shift(8, 2))
+ self.assertEqual(3, number.ceil_shift(9, 2))
+
+ for b in range(3, 1+129, 3): # 3, 6, ... , 129
+ self.assertEqual(0, number.ceil_shift(0, b))
+
+ n = 1L
+ while n <= 2L**(b+2):
+ (q, r) = divmod(n-1, 2L**b)
+ expected = q + int(not not r)
+ self.assertEqual((n-1, b, expected),
+ (n-1, b, number.ceil_shift(n-1, b)))
+
+ (q, r) = divmod(n, 2L**b)
+ expected = q + int(not not r)
+ self.assertEqual((n, b, expected),
+ (n, b, number.ceil_shift(n, b)))
+
+ (q, r) = divmod(n+1, 2L**b)
+ expected = q + int(not not r)
+ self.assertEqual((n+1, b, expected),
+ (n+1, b, number.ceil_shift(n+1, b)))
+
+ n *= 2
+
+ def test_ceil_div(self):
+ """Util.number.ceil_div"""
+ self.assertRaises(TypeError, number.ceil_div, "1", 1)
+ self.assertRaises(ZeroDivisionError, number.ceil_div, 1, 0)
+ self.assertRaises(ZeroDivisionError, number.ceil_div, -1, 0)
+
+ # b = -1
+ self.assertEqual(0, number.ceil_div(0, -1))
+ self.assertEqual(-1, number.ceil_div(1, -1))
+ self.assertEqual(-2, number.ceil_div(2, -1))
+ self.assertEqual(-3, number.ceil_div(3, -1))
+
+ # b = 1
+ self.assertEqual(0, number.ceil_div(0, 1))
+ self.assertEqual(1, number.ceil_div(1, 1))
+ self.assertEqual(2, number.ceil_div(2, 1))
+ self.assertEqual(3, number.ceil_div(3, 1))
+
+ # b = 2
+ self.assertEqual(0, number.ceil_div(0, 2))
+ self.assertEqual(1, number.ceil_div(1, 2))
+ self.assertEqual(1, number.ceil_div(2, 2))
+ self.assertEqual(2, number.ceil_div(3, 2))
+ self.assertEqual(2, number.ceil_div(4, 2))
+ self.assertEqual(3, number.ceil_div(5, 2))
+
+ # b = 3
+ self.assertEqual(0, number.ceil_div(0, 3))
+ self.assertEqual(1, number.ceil_div(1, 3))
+ self.assertEqual(1, number.ceil_div(2, 3))
+ self.assertEqual(1, number.ceil_div(3, 3))
+ self.assertEqual(2, number.ceil_div(4, 3))
+ self.assertEqual(2, number.ceil_div(5, 3))
+ self.assertEqual(2, number.ceil_div(6, 3))
+ self.assertEqual(3, number.ceil_div(7, 3))
+
+ # b = 4
+ self.assertEqual(0, number.ceil_div(0, 4))
+ self.assertEqual(1, number.ceil_div(1, 4))
+ self.assertEqual(1, number.ceil_div(2, 4))
+ self.assertEqual(1, number.ceil_div(3, 4))
+ self.assertEqual(1, number.ceil_div(4, 4))
+ self.assertEqual(2, number.ceil_div(5, 4))
+ self.assertEqual(2, number.ceil_div(6, 4))
+ self.assertEqual(2, number.ceil_div(7, 4))
+ self.assertEqual(2, number.ceil_div(8, 4))
+ self.assertEqual(3, number.ceil_div(9, 4))
+
+ # b = -4
+ self.assertEqual(3, number.ceil_div(-9, -4))
+ self.assertEqual(2, number.ceil_div(-8, -4))
+ self.assertEqual(2, number.ceil_div(-7, -4))
+ self.assertEqual(2, number.ceil_div(-6, -4))
+ self.assertEqual(2, number.ceil_div(-5, -4))
+ self.assertEqual(1, number.ceil_div(-4, -4))
+ self.assertEqual(1, number.ceil_div(-3, -4))
+ self.assertEqual(1, number.ceil_div(-2, -4))
+ self.assertEqual(1, number.ceil_div(-1, -4))
+ self.assertEqual(0, number.ceil_div(0, -4))
+ self.assertEqual(0, number.ceil_div(1, -4))
+ self.assertEqual(0, number.ceil_div(2, -4))
+ self.assertEqual(0, number.ceil_div(3, -4))
+ self.assertEqual(-1, number.ceil_div(4, -4))
+ self.assertEqual(-1, number.ceil_div(5, -4))
+ self.assertEqual(-1, number.ceil_div(6, -4))
+ self.assertEqual(-1, number.ceil_div(7, -4))
+ self.assertEqual(-2, number.ceil_div(8, -4))
+ self.assertEqual(-2, number.ceil_div(9, -4))
+
+ def test_exact_log2(self):
+ """Util.number.exact_log2"""
+ self.assertRaises(TypeError, number.exact_log2, "0")
+ self.assertRaises(ValueError, number.exact_log2, -1)
+ self.assertRaises(ValueError, number.exact_log2, 0)
+ self.assertEqual(0, number.exact_log2(1))
+ self.assertEqual(1, number.exact_log2(2))
+ self.assertRaises(ValueError, number.exact_log2, 3)
+ self.assertEqual(2, number.exact_log2(4))
+ self.assertRaises(ValueError, number.exact_log2, 5)
+ self.assertRaises(ValueError, number.exact_log2, 6)
+ self.assertRaises(ValueError, number.exact_log2, 7)
+ e = 3
+ n = 8
+ while e < 16:
+ if n == 2**e:
+ self.assertEqual(e, number.exact_log2(n), "expected=2**%d, n=%d" % (e, n))
+ e += 1
+ else:
+ self.assertRaises(ValueError, number.exact_log2, n)
+ n += 1
+
+ for e in range(16, 1+64, 2):
+ self.assertRaises(ValueError, number.exact_log2, 2L**e-1)
+ self.assertEqual(e, number.exact_log2(2L**e))
+ self.assertRaises(ValueError, number.exact_log2, 2L**e+1)
+
+ def test_exact_div(self):
+ """Util.number.exact_div"""
+
+ # Positive numbers
+ self.assertEqual(1, number.exact_div(1, 1))
+ self.assertRaises(ValueError, number.exact_div, 1, 2)
+ self.assertEqual(1, number.exact_div(2, 2))
+ self.assertRaises(ValueError, number.exact_div, 3, 2)
+ self.assertEqual(2, number.exact_div(4, 2))
+
+ # Negative numbers
+ self.assertEqual(-1, number.exact_div(-1, 1))
+ self.assertEqual(-1, number.exact_div(1, -1))
+ self.assertRaises(ValueError, number.exact_div, -1, 2)
+ self.assertEqual(1, number.exact_div(-2, -2))
+ self.assertEqual(-2, number.exact_div(-4, 2))
+
+ # Zero dividend
+ self.assertEqual(0, number.exact_div(0, 1))
+ self.assertEqual(0, number.exact_div(0, 2))
+
+ # Zero divisor (allow_divzero == False)
+ self.assertRaises(ZeroDivisionError, number.exact_div, 0, 0)
+ self.assertRaises(ZeroDivisionError, number.exact_div, 1, 0)
+
+ # Zero divisor (allow_divzero == True)
+ self.assertEqual(0, number.exact_div(0, 0, allow_divzero=True))
+ self.assertRaises(ValueError, number.exact_div, 1, 0, allow_divzero=True)
+
+ def test_floor_div(self):
+ """Util.number.floor_div"""
+ self.assertRaises(TypeError, number.floor_div, "1", 1)
+ for a in range(-10, 10):
+ for b in range(-10, 10):
+ if b == 0:
+ self.assertRaises(ZeroDivisionError, number.floor_div, a, b)
+ else:
+ self.assertEqual((a, b, int(math.floor(float(a) / b))),
+ (a, b, number.floor_div(a, b)))
+
+ def test_getStrongPrime(self):
+ """Util.number.getStrongPrime"""
+ self.assertRaises(ValueError, number.getStrongPrime, 256)
+ self.assertRaises(ValueError, number.getStrongPrime, 513)
+ bits = 512
+ x = number.getStrongPrime(bits)
+ self.assertNotEqual(x % 2, 0)
+ self.assertEqual(x > (1L << bits-1)-1, 1)
+ self.assertEqual(x < (1L << bits), 1)
+ e = 2**16+1
+ x = number.getStrongPrime(bits, e)
+ self.assertEqual(number.GCD(x-1, e), 1)
+ self.assertNotEqual(x % 2, 0)
+ self.assertEqual(x > (1L << bits-1)-1, 1)
+ self.assertEqual(x < (1L << bits), 1)
+ e = 2**16+2
+ x = number.getStrongPrime(bits, e)
+ self.assertEqual(number.GCD((x-1)>>1, e), 1)
+ self.assertNotEqual(x % 2, 0)
+ self.assertEqual(x > (1L << bits-1)-1, 1)
+ self.assertEqual(x < (1L << bits), 1)
+
+ def test_isPrime(self):
+ """Util.number.isPrime"""
+ self.assertEqual(number.isPrime(-3), False) # Regression test: negative numbers should not be prime
+ self.assertEqual(number.isPrime(-2), False) # Regression test: negative numbers should not be prime
+ self.assertEqual(number.isPrime(1), False) # Regression test: isPrime(1) caused some versions of PyCrypto to crash.
+ self.assertEqual(number.isPrime(2), True)
+ self.assertEqual(number.isPrime(3), True)
+ self.assertEqual(number.isPrime(4), False)
+ self.assertEqual(number.isPrime(2L**1279-1), True)
+ self.assertEqual(number.isPrime(-(2L**1279-1)), False) # Regression test: negative numbers should not be prime
+ # test some known gmp pseudo-primes taken from
+ # http://www.trnicely.net/misc/mpzspsp.html
+ for composite in (43 * 127 * 211, 61 * 151 * 211, 15259 * 30517,
+ 346141L * 692281L, 1007119L * 2014237L, 3589477L * 7178953L,
+ 4859419L * 9718837L, 2730439L * 5460877L,
+ 245127919L * 490255837L, 963939391L * 1927878781L,
+ 4186358431L * 8372716861L, 1576820467L * 3153640933L):
+ self.assertEqual(number.isPrime(long(composite)), False)
+
+ def test_size(self):
+ self.assertEqual(number.size(2),2)
+ self.assertEqual(number.size(3),2)
+ self.assertEqual(number.size(0xa2),8)
+ self.assertEqual(number.size(0xa2ba40),8*3)
+ self.assertEqual(number.size(0xa2ba40ee07e3b2bd2f02ce227f36a195024486e49c19cb41bbbdfbba98b22b0e577c2eeaffa20d883a76e65e394c69d4b3c05a1e8fadda27edb2a42bc000fe888b9b32c22d15add0cd76b3e7936e19955b220dd17d4ea904b1ec102b2e4de7751222aa99151024c7cb41cc5ea21d00eeb41f7c800834d2c6e06bce3bce7ea9a5L), 1024)
+
+ def test_negative_number_roundtrip_mpzToLongObj_longObjToMPZ(self):
+ """Test that mpzToLongObj and longObjToMPZ (internal functions) roundtrip negative numbers correctly."""
+ n = -100000000000000000000000000000000000L
+ e = 2L
+ k = number._fastmath.rsa_construct(n, e)
+ self.assertEqual(n, k.n)
+ self.assertEqual(e, k.e)
+
+def get_tests(config={}):
+ from Crypto.SelfTest.st_common import list_test_cases
+ return list_test_cases(MiscTests)
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/Util/test_winrandom.py b/lib/Python/Lib/Crypto/SelfTest/Util/test_winrandom.py
new file mode 100644
index 000000000..3fc5145ac
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/Util/test_winrandom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/Util/test_winrandom.py: Self-test for the winrandom module
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self-test suite for Crypto.Util.winrandom"""
+
+__revision__ = "$Id$"
+
+import unittest
+
+class WinRandomImportTest(unittest.TestCase):
+ def runTest(self):
+ """winrandom: simple test"""
+ # Import the winrandom module and try to use it
+ from Crypto.Util import winrandom
+ randobj = winrandom.new()
+ x = randobj.get_bytes(16)
+ y = randobj.get_bytes(16)
+ self.assertNotEqual(x, y)
+
+def get_tests(config={}):
+ return [WinRandomImportTest()]
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/__init__.py b/lib/Python/Lib/Crypto/SelfTest/__init__.py
new file mode 100644
index 000000000..40b39693a
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/__init__.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/__init__.py: Self-test for PyCrypto
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Self tests
+
+These tests should perform quickly and can ideally be used every time an
+application runs.
+"""
+
+__revision__ = "$Id$"
+
+import sys
+import unittest
+from StringIO import StringIO
+
+class SelfTestError(Exception):
+ def __init__(self, message, result):
+ Exception.__init__(self, message, result)
+ self.message = message
+ self.result = result
+
+def run(module=None, verbosity=0, stream=None, tests=None, config=None, **kwargs):
+ """Execute self-tests.
+
+ This raises SelfTestError if any test is unsuccessful.
+
+ You may optionally pass in a sub-module of SelfTest if you only want to
+ perform some of the tests. For example, the following would test only the
+ hash modules:
+
+ Crypto.SelfTest.run(Crypto.SelfTest.Hash)
+
+ """
+ if config is None:
+ config = {}
+ suite = unittest.TestSuite()
+ if module is None:
+ if tests is None:
+ tests = get_tests(config=config)
+ suite.addTests(tests)
+ else:
+ if tests is None:
+ suite.addTests(module.get_tests(config=config))
+ else:
+ raise ValueError("'module' and 'tests' arguments are mutually exclusive")
+ if stream is None:
+ kwargs['stream'] = StringIO()
+ runner = unittest.TextTestRunner(verbosity=verbosity, **kwargs)
+ result = runner.run(suite)
+ if not result.wasSuccessful():
+ if stream is None:
+ sys.stderr.write(stream.getvalue())
+ raise SelfTestError("Self-test failed", result)
+ return result
+
+def get_tests(config={}):
+ tests = []
+ from Crypto.SelfTest import Cipher; tests += Cipher.get_tests(config=config)
+ from Crypto.SelfTest import Hash; tests += Hash.get_tests(config=config)
+ from Crypto.SelfTest import Protocol; tests += Protocol.get_tests(config=config)
+ from Crypto.SelfTest import PublicKey; tests += PublicKey.get_tests(config=config)
+ from Crypto.SelfTest import Random; tests += Random.get_tests(config=config)
+ from Crypto.SelfTest import Util; tests += Util.get_tests(config=config)
+ from Crypto.SelfTest import Signature; tests += Signature.get_tests(config=config)
+ return tests
+
+if __name__ == '__main__':
+ suite = lambda: unittest.TestSuite(get_tests())
+ unittest.main(defaultTest='suite')
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/SelfTest/st_common.py b/lib/Python/Lib/Crypto/SelfTest/st_common.py
new file mode 100644
index 000000000..c56eac5f1
--- /dev/null
+++ b/lib/Python/Lib/Crypto/SelfTest/st_common.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+#
+# SelfTest/st_common.py: Common functions for SelfTest modules
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Common functions for SelfTest modules"""
+
+__revision__ = "$Id$"
+
+import unittest
+import binascii
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+class _list_testloader(unittest.TestLoader):
+ suiteClass = list
+
+def list_test_cases(class_):
+ """Return a list of TestCase instances given a TestCase class
+
+ This is useful when you have defined test* methods on your TestCase class.
+ """
+ return _list_testloader().loadTestsFromTestCase(class_)
+
+def strip_whitespace(s):
+ """Remove whitespace from a text or byte string"""
+ if isinstance(s,str):
+ return b("".join(s.split()))
+ else:
+ return b("").join(s.split())
+
+def a2b_hex(s):
+ """Convert hexadecimal to binary, ignoring whitespace"""
+ return binascii.a2b_hex(strip_whitespace(s))
+
+def b2a_hex(s):
+ """Convert binary to hexadecimal"""
+ # For completeness
+ return binascii.b2a_hex(s)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Signature/PKCS1_PSS.py b/lib/Python/Lib/Crypto/Signature/PKCS1_PSS.py
new file mode 100644
index 000000000..4f50eb854
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Signature/PKCS1_PSS.py
@@ -0,0 +1,355 @@
+# -*- coding: utf-8 -*-
+#
+# Signature/PKCS1_PSS.py : PKCS#1 PPS
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""RSA digital signature protocol with appendix according to PKCS#1 PSS.
+
+See RFC3447__ or the `original RSA Labs specification`__.
+
+This scheme is more properly called ``RSASSA-PSS``.
+
+For example, a sender may authenticate a message using SHA-1 and PSS like
+this:
+
+ >>> from Crypto.Signature import PKCS1_PSS
+ >>> from Crypto.Hash import SHA
+ >>> from Crypto.PublicKey import RSA
+ >>> from Crypto import Random
+ >>>
+ >>> message = 'To be signed'
+ >>> key = RSA.importKey(open('privkey.der').read())
+ >>> h = SHA.new()
+ >>> h.update(message)
+ >>> signer = PKCS1_PSS.new(key)
+ >>> signature = PKCS1_PSS.sign(key)
+
+At the receiver side, verification can be done like using the public part of
+the RSA key:
+
+ >>> key = RSA.importKey(open('pubkey.der').read())
+ >>> h = SHA.new()
+ >>> h.update(message)
+ >>> verifier = PKCS1_PSS.new(key)
+ >>> if verifier.verify(h, signature):
+ >>> print "The signature is authentic."
+ >>> else:
+ >>> print "The signature is not authentic."
+
+:undocumented: __revision__, __package__
+
+.. __: http://www.ietf.org/rfc/rfc3447.txt
+.. __: http://www.rsa.com/rsalabs/node.asp?id=2125
+"""
+
+# Allow nested scopes in Python 2.1
+# See http://oreilly.com/pub/a/python/2001/04/19/pythonnews.html
+from __future__ import nested_scopes
+
+__revision__ = "$Id$"
+__all__ = [ 'new', 'PSS_SigScheme' ]
+
+from Crypto.Util.py3compat import *
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+import Crypto.Util.number
+from Crypto.Util.number import ceil_shift, ceil_div, long_to_bytes
+from Crypto.Util.strxor import strxor
+
+class PSS_SigScheme:
+ """This signature scheme can perform PKCS#1 PSS RSA signature or verification."""
+
+ def __init__(self, key, mgfunc, saltLen):
+ """Initialize this PKCS#1 PSS signature scheme object.
+
+ :Parameters:
+ key : an RSA key object
+ If a private half is given, both signature and verification are possible.
+ If a public half is given, only verification is possible.
+ mgfunc : callable
+ A mask generation function that accepts two parameters: a string to
+ use as seed, and the lenth of the mask to generate, in bytes.
+ saltLen : int
+ Length of the salt, in bytes.
+ """
+ self._key = key
+ self._saltLen = saltLen
+ self._mgfunc = mgfunc
+
+ def can_sign(self):
+ """Return True if this cipher object can be used for signing messages."""
+ return self._key.has_private()
+
+ def sign(self, mhash):
+ """Produce the PKCS#1 PSS signature of a message.
+
+ This function is named ``RSASSA-PSS-SIGN``, and is specified in
+ section 8.1.1 of RFC3447.
+
+ :Parameters:
+ mhash : hash object
+ The hash that was carried out over the message. This is an object
+ belonging to the `Crypto.Hash` module.
+
+ :Return: The PSS signature encoded as a string.
+ :Raise ValueError:
+ If the RSA key length is not sufficiently long to deal with the given
+ hash algorithm.
+ :Raise TypeError:
+ If the RSA key has no private half.
+
+ :attention: Modify the salt length and the mask generation function only
+ if you know what you are doing.
+ The receiver must use the same parameters too.
+ """
+ # TODO: Verify the key is RSA
+
+ randfunc = self._key._randfunc
+
+ # Set defaults for salt length and mask generation function
+ if self._saltLen == None:
+ sLen = mhash.digest_size
+ else:
+ sLen = self._saltLen
+ if self._mgfunc:
+ mgf = self._mgfunc
+ else:
+ mgf = lambda x,y: MGF1(x,y,mhash)
+
+ modBits = Crypto.Util.number.size(self._key.n)
+
+ # See 8.1.1 in RFC3447
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+ # Step 1
+ em = EMSA_PSS_ENCODE(mhash, modBits-1, randfunc, mgf, sLen)
+ # Step 2a (OS2IP) and 2b (RSASP1)
+ m = self._key.decrypt(em)
+ # Step 2c (I2OSP)
+ S = bchr(0x00)*(k-len(m)) + m
+ return S
+
+ def verify(self, mhash, S):
+ """Verify that a certain PKCS#1 PSS signature is authentic.
+
+ This function checks if the party holding the private half of the given
+ RSA key has really signed the message.
+
+ This function is called ``RSASSA-PSS-VERIFY``, and is specified in section
+ 8.1.2 of RFC3447.
+
+ :Parameters:
+ mhash : hash object
+ The hash that was carried out over the message. This is an object
+ belonging to the `Crypto.Hash` module.
+ S : string
+ The signature that needs to be validated.
+
+ :Return: True if verification is correct. False otherwise.
+ """
+ # TODO: Verify the key is RSA
+
+ # Set defaults for salt length and mask generation function
+ if self._saltLen == None:
+ sLen = mhash.digest_size
+ else:
+ sLen = self._saltLen
+ if self._mgfunc:
+ mgf = self._mgfunc
+ else:
+ mgf = lambda x,y: MGF1(x,y,mhash)
+
+ modBits = Crypto.Util.number.size(self._key.n)
+
+ # See 8.1.2 in RFC3447
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+ # Step 1
+ if len(S) != k:
+ return False
+ # Step 2a (O2SIP), 2b (RSAVP1), and partially 2c (I2OSP)
+ # Note that signature must be smaller than the module
+ # but RSA.py won't complain about it.
+ # TODO: Fix RSA object; don't do it here.
+ em = self._key.encrypt(S, 0)[0]
+ # Step 2c
+ emLen = ceil_div(modBits-1,8)
+ em = bchr(0x00)*(emLen-len(em)) + em
+ # Step 3
+ try:
+ result = EMSA_PSS_VERIFY(mhash, em, modBits-1, mgf, sLen)
+ except ValueError:
+ return False
+ # Step 4
+ return result
+
+def MGF1(mgfSeed, maskLen, hash):
+ """Mask Generation Function, described in B.2.1"""
+ T = b("")
+ for counter in xrange(ceil_div(maskLen, hash.digest_size)):
+ c = long_to_bytes(counter, 4)
+ T = T + hash.new(mgfSeed + c).digest()
+ assert(len(T)>=maskLen)
+ return T[:maskLen]
+
+def EMSA_PSS_ENCODE(mhash, emBits, randFunc, mgf, sLen):
+ """
+ Implement the ``EMSA-PSS-ENCODE`` function, as defined
+ in PKCS#1 v2.1 (RFC3447, 9.1.1).
+
+ The original ``EMSA-PSS-ENCODE`` actually accepts the message ``M`` as input,
+ and hash it internally. Here, we expect that the message has already
+ been hashed instead.
+
+ :Parameters:
+ mhash : hash object
+ The hash object that holds the digest of the message being signed.
+ emBits : int
+ Maximum length of the final encoding, in bits.
+ randFunc : callable
+ An RNG function that accepts as only parameter an int, and returns
+ a string of random bytes, to be used as salt.
+ mgf : callable
+ A mask generation function that accepts two parameters: a string to
+ use as seed, and the lenth of the mask to generate, in bytes.
+ sLen : int
+ Length of the salt, in bytes.
+
+ :Return: An ``emLen`` byte long string that encodes the hash
+ (with ``emLen = \ceil(emBits/8)``).
+
+ :Raise ValueError:
+ When digest or salt length are too big.
+ """
+
+ emLen = ceil_div(emBits,8)
+
+ # Bitmask of digits that fill up
+ lmask = 0
+ for i in xrange(8*emLen-emBits):
+ lmask = lmask>>1 | 0x80
+
+ # Step 1 and 2 have been already done
+ # Step 3
+ if emLen < mhash.digest_size+sLen+2:
+ raise ValueError("Digest or salt length are too long for given key size.")
+ # Step 4
+ salt = b("")
+ if randFunc and sLen>0:
+ salt = randFunc(sLen)
+ # Step 5 and 6
+ h = mhash.new(bchr(0x00)*8 + mhash.digest() + salt)
+ # Step 7 and 8
+ db = bchr(0x00)*(emLen-sLen-mhash.digest_size-2) + bchr(0x01) + salt
+ # Step 9
+ dbMask = mgf(h.digest(), emLen-mhash.digest_size-1)
+ # Step 10
+ maskedDB = strxor(db,dbMask)
+ # Step 11
+ maskedDB = bchr(bord(maskedDB[0]) & ~lmask) + maskedDB[1:]
+ # Step 12
+ em = maskedDB + h.digest() + bchr(0xBC)
+ return em
+
+def EMSA_PSS_VERIFY(mhash, em, emBits, mgf, sLen):
+ """
+ Implement the ``EMSA-PSS-VERIFY`` function, as defined
+ in PKCS#1 v2.1 (RFC3447, 9.1.2).
+
+ ``EMSA-PSS-VERIFY`` actually accepts the message ``M`` as input,
+ and hash it internally. Here, we expect that the message has already
+ been hashed instead.
+
+ :Parameters:
+ mhash : hash object
+ The hash object that holds the digest of the message to be verified.
+ em : string
+ The signature to verify, therefore proving that the sender really signed
+ the message that was received.
+ emBits : int
+ Length of the final encoding (em), in bits.
+ mgf : callable
+ A mask generation function that accepts two parameters: a string to
+ use as seed, and the lenth of the mask to generate, in bytes.
+ sLen : int
+ Length of the salt, in bytes.
+
+ :Return: 0 if the encoding is consistent, 1 if it is inconsistent.
+
+ :Raise ValueError:
+ When digest or salt length are too big.
+ """
+
+ emLen = ceil_div(emBits,8)
+
+ # Bitmask of digits that fill up
+ lmask = 0
+ for i in xrange(8*emLen-emBits):
+ lmask = lmask>>1 | 0x80
+
+ # Step 1 and 2 have been already done
+ # Step 3
+ if emLen < mhash.digest_size+sLen+2:
+ return False
+ # Step 4
+ if ord(em[-1:])!=0xBC:
+ return False
+ # Step 5
+ maskedDB = em[:emLen-mhash.digest_size-1]
+ h = em[emLen-mhash.digest_size-1:-1]
+ # Step 6
+ if lmask & bord(em[0]):
+ return False
+ # Step 7
+ dbMask = mgf(h, emLen-mhash.digest_size-1)
+ # Step 8
+ db = strxor(maskedDB, dbMask)
+ # Step 9
+ db = bchr(bord(db[0]) & ~lmask) + db[1:]
+ # Step 10
+ if not db.startswith(bchr(0x00)*(emLen-mhash.digest_size-sLen-2) + bchr(0x01)):
+ return False
+ # Step 11
+ salt = b("")
+ if sLen: salt = db[-sLen:]
+ # Step 12 and 13
+ hp = mhash.new(bchr(0x00)*8 + mhash.digest() + salt).digest()
+ # Step 14
+ if h!=hp:
+ return False
+ return True
+
+def new(key, mgfunc=None, saltLen=None):
+ """Return a signature scheme object `PSS_SigScheme` that
+ can be used to perform PKCS#1 PSS signature or verification.
+
+ :Parameters:
+ key : RSA key object
+ The key to use to sign or verify the message. This is a `Crypto.PublicKey.RSA` object.
+ Signing is only possible if *key* is a private RSA key.
+ mgfunc : callable
+ A mask generation function that accepts two parameters: a string to
+ use as seed, and the lenth of the mask to generate, in bytes.
+ If not specified, the standard MGF1 is used.
+ saltLen : int
+ Length of the salt, in bytes. If not specified, it matches the output
+ size of the hash function.
+
+ """
+ return PSS_SigScheme(key, mgfunc, saltLen)
+
diff --git a/lib/Python/Lib/Crypto/Signature/PKCS1_v1_5.py b/lib/Python/Lib/Crypto/Signature/PKCS1_v1_5.py
new file mode 100644
index 000000000..73ac251c2
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Signature/PKCS1_v1_5.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+#
+# Signature/PKCS1-v1_5.py : PKCS#1 v1.5
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""
+RSA digital signature protocol according to PKCS#1 v1.5
+
+See RFC3447__ or the `original RSA Labs specification`__.
+
+This scheme is more properly called ``RSASSA-PKCS1-v1_5``.
+
+For example, a sender may authenticate a message using SHA-1 like
+this:
+
+ >>> from Crypto.Signature import PKCS1_v1_5
+ >>> from Crypto.Hash import SHA
+ >>> from Crypto.PublicKey import RSA
+ >>>
+ >>> message = 'To be signed'
+ >>> key = RSA.importKey(open('privkey.der').read())
+ >>> h = SHA.new(message)
+ >>> signer = PKCS1_v1_5.new(key)
+ >>> signature = signer.sign(h)
+
+At the receiver side, verification can be done using the public part of
+the RSA key:
+
+ >>> key = RSA.importKey(open('pubkey.der').read())
+ >>> h = SHA.new(message)
+ >>> verifier = PKCS1_v1_5.new(key)
+ >>> if verifier.verify(h, signature):
+ >>> print "The signature is authentic."
+ >>> else:
+ >>> print "The signature is not authentic."
+
+:undocumented: __revision__, __package__
+
+.. __: http://www.ietf.org/rfc/rfc3447.txt
+.. __: http://www.rsa.com/rsalabs/node.asp?id=2125
+"""
+
+__revision__ = "$Id$"
+__all__ = [ 'new', 'PKCS115_SigScheme' ]
+
+import Crypto.Util.number
+from Crypto.Util.number import ceil_div
+from Crypto.Util.asn1 import DerSequence, DerNull, DerOctetString
+from Crypto.Util.py3compat import *
+
+class PKCS115_SigScheme:
+ """This signature scheme can perform PKCS#1 v1.5 RSA signature or verification."""
+
+ def __init__(self, key):
+ """Initialize this PKCS#1 v1.5 signature scheme object.
+
+ :Parameters:
+ key : an RSA key object
+ If a private half is given, both signature and verification are possible.
+ If a public half is given, only verification is possible.
+ """
+ self._key = key
+
+ def can_sign(self):
+ """Return True if this cipher object can be used for signing messages."""
+ return self._key.has_private()
+
+ def sign(self, mhash):
+ """Produce the PKCS#1 v1.5 signature of a message.
+
+ This function is named ``RSASSA-PKCS1-V1_5-SIGN``, and is specified in
+ section 8.2.1 of RFC3447.
+
+ :Parameters:
+ mhash : hash object
+ The hash that was carried out over the message. This is an object
+ belonging to the `Crypto.Hash` module.
+
+ :Return: The signature encoded as a string.
+ :Raise ValueError:
+ If the RSA key length is not sufficiently long to deal with the given
+ hash algorithm.
+ :Raise TypeError:
+ If the RSA key has no private half.
+ """
+ # TODO: Verify the key is RSA
+
+ # See 8.2.1 in RFC3447
+ modBits = Crypto.Util.number.size(self._key.n)
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+
+ # Step 1
+ em = EMSA_PKCS1_V1_5_ENCODE(mhash, k)
+ # Step 2a (OS2IP) and 2b (RSASP1)
+ m = self._key.decrypt(em)
+ # Step 2c (I2OSP)
+ S = bchr(0x00)*(k-len(m)) + m
+ return S
+
+ def verify(self, mhash, S):
+ """Verify that a certain PKCS#1 v1.5 signature is authentic.
+
+ This function checks if the party holding the private half of the key
+ really signed the message.
+
+ This function is named ``RSASSA-PKCS1-V1_5-VERIFY``, and is specified in
+ section 8.2.2 of RFC3447.
+
+ :Parameters:
+ mhash : hash object
+ The hash that was carried out over the message. This is an object
+ belonging to the `Crypto.Hash` module.
+ S : string
+ The signature that needs to be validated.
+
+ :Return: True if verification is correct. False otherwise.
+ """
+ # TODO: Verify the key is RSA
+
+ # See 8.2.2 in RFC3447
+ modBits = Crypto.Util.number.size(self._key.n)
+ k = ceil_div(modBits,8) # Convert from bits to bytes
+
+ # Step 1
+ if len(S) != k:
+ return 0
+ # Step 2a (O2SIP) and 2b (RSAVP1)
+ # Note that signature must be smaller than the module
+ # but RSA.py won't complain about it.
+ # TODO: Fix RSA object; don't do it here.
+ m = self._key.encrypt(S, 0)[0]
+ # Step 2c (I2OSP)
+ em1 = bchr(0x00)*(k-len(m)) + m
+ # Step 3
+ try:
+ em2 = EMSA_PKCS1_V1_5_ENCODE(mhash, k)
+ except ValueError:
+ return 0
+ # Step 4
+ # By comparing the full encodings (as opposed to checking each
+ # of its components one at a time) we avoid attacks to the padding
+ # scheme like Bleichenbacher's (see http://www.mail-archive.com/cryptography@metzdowd.com/msg06537).
+ #
+ return em1==em2
+
+def EMSA_PKCS1_V1_5_ENCODE(hash, emLen):
+ """
+ Implement the ``EMSA-PKCS1-V1_5-ENCODE`` function, as defined
+ in PKCS#1 v2.1 (RFC3447, 9.2).
+
+ ``EMSA-PKCS1-V1_5-ENCODE`` actually accepts the message ``M`` as input,
+ and hash it internally. Here, we expect that the message has already
+ been hashed instead.
+
+ :Parameters:
+ hash : hash object
+ The hash object that holds the digest of the message being signed.
+ emLen : int
+ The length the final encoding must have, in bytes.
+
+ :attention: the early standard (RFC2313) stated that ``DigestInfo``
+ had to be BER-encoded. This means that old signatures
+ might have length tags in indefinite form, which
+ is not supported in DER. Such encoding cannot be
+ reproduced by this function.
+
+ :attention: the same standard defined ``DigestAlgorithm`` to be
+ of ``AlgorithmIdentifier`` type, where the PARAMETERS
+ item is optional. Encodings for ``MD2/4/5`` without
+ ``PARAMETERS`` cannot be reproduced by this function.
+
+ :Return: An ``emLen`` byte long string that encodes the hash.
+ """
+
+ # First, build the ASN.1 DER object DigestInfo:
+ #
+ # DigestInfo ::= SEQUENCE {
+ # digestAlgorithm AlgorithmIdentifier,
+ # digest OCTET STRING
+ # }
+ #
+ # where digestAlgorithm identifies the hash function and shall be an
+ # algorithm ID with an OID in the set PKCS1-v1-5DigestAlgorithms.
+ #
+ # PKCS1-v1-5DigestAlgorithms ALGORITHM-IDENTIFIER ::= {
+ # { OID id-md2 PARAMETERS NULL }|
+ # { OID id-md5 PARAMETERS NULL }|
+ # { OID id-sha1 PARAMETERS NULL }|
+ # { OID id-sha256 PARAMETERS NULL }|
+ # { OID id-sha384 PARAMETERS NULL }|
+ # { OID id-sha512 PARAMETERS NULL }
+ # }
+ #
+ digestAlgo = DerSequence([hash.oid, DerNull().encode()])
+ digest = DerOctetString(hash.digest())
+ digestInfo = DerSequence([
+ digestAlgo.encode(),
+ digest.encode()
+ ]).encode()
+
+ # We need at least 11 bytes for the remaining data: 3 fixed bytes and
+ # at least 8 bytes of padding).
+ if emLen<len(digestInfo)+11:
+ raise ValueError("Selected hash algorith has a too long digest (%d bytes)." % len(digest))
+ PS = bchr(0xFF) * (emLen - len(digestInfo) - 3)
+ return b("\x00\x01") + PS + bchr(0x00) + digestInfo
+
+def new(key):
+ """Return a signature scheme object `PKCS115_SigScheme` that
+ can be used to perform PKCS#1 v1.5 signature or verification.
+
+ :Parameters:
+ key : RSA key object
+ The key to use to sign or verify the message. This is a `Crypto.PublicKey.RSA` object.
+ Signing is only possible if *key* is a private RSA key.
+
+ """
+ return PKCS115_SigScheme(key)
+
diff --git a/lib/Python/Lib/Crypto/Signature/__init__.py b/lib/Python/Lib/Crypto/Signature/__init__.py
new file mode 100644
index 000000000..ed523b42c
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Signature/__init__.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Digital signature protocols
+
+A collection of standardized protocols to carry out digital signatures.
+
+:undocumented: __revision__, __package__
+"""
+
+__all__ = [ 'PKCS1_v1_5', 'PKCS1_PSS' ]
+__revision__ = "$Id$"
+
+
diff --git a/lib/Python/Lib/Crypto/Util/Counter.py b/lib/Python/Lib/Crypto/Util/Counter.py
new file mode 100644
index 000000000..01b804a69
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/Counter.py
@@ -0,0 +1,127 @@
+# -*- coding: ascii -*-
+#
+# Util/Counter.py : Fast counter for use with CTR-mode ciphers
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+"""Fast counter functions for CTR cipher modes.
+
+CTR is a chaining mode for symmetric block encryption or decryption.
+Messages are divideded into blocks, and the cipher operation takes
+place on each block using the secret key and a unique *counter block*.
+
+The most straightforward way to fulfil the uniqueness property is
+to start with an initial, random *counter block* value, and increment it as
+the next block is processed.
+
+The block ciphers from `Crypto.Cipher` (when configured in *MODE_CTR* mode)
+invoke a callable object (the *counter* parameter) to get the next *counter block*.
+Unfortunately, the Python calling protocol leads to major performance degradations.
+
+The counter functions instantiated by this module will be invoked directly
+by the ciphers in `Crypto.Cipher`. The fact that the Python layer is bypassed
+lead to more efficient (and faster) execution of CTR cipher modes.
+
+An example of usage is the following:
+
+ >>> from Crypto.Cipher import AES
+ >>> from Crypto.Util import Counter
+ >>>
+ >>> pt = b'\x00'*1000000
+ >>> ctr = Counter.new(128)
+ >>> cipher = AES.new(b'\x00'*16, AES.MODE_CTR, counter=ctr)
+ >>> ct = cipher.encrypt(pt)
+
+:undocumented: __package__
+"""
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+from Crypto.Util.py3compat import *
+
+from Crypto.Util import _counter
+import struct
+
+# Factory function
+def new(nbits, prefix=b(""), suffix=b(""), initial_value=1, overflow=0, little_endian=False, allow_wraparound=False, disable_shortcut=False):
+ """Create a stateful counter block function suitable for CTR encryption modes.
+
+ Each call to the function returns the next counter block.
+ Each counter block is made up by three parts::
+
+ prefix || counter value || postfix
+
+ The counter value is incremented by one at each call.
+
+ :Parameters:
+ nbits : integer
+ Length of the desired counter, in bits. It must be a multiple of 8.
+ prefix : byte string
+ The constant prefix of the counter block. By default, no prefix is
+ used.
+ suffix : byte string
+ The constant postfix of the counter block. By default, no suffix is
+ used.
+ initial_value : integer
+ The initial value of the counter. Default value is 1.
+ little_endian : boolean
+ If True, the counter number will be encoded in little endian format.
+ If False (default), in big endian format.
+ allow_wraparound : boolean
+ If True, the function will raise an *OverflowError* exception as soon
+ as the counter wraps around. If False (default), the counter will
+ simply restart from zero.
+ disable_shortcut : boolean
+ If True, do not make ciphers from `Crypto.Cipher` bypass the Python
+ layer when invoking the counter block function.
+ If False (default), bypass the Python layer.
+ :Returns:
+ The counter block function.
+ """
+
+ # Sanity-check the message size
+ (nbytes, remainder) = divmod(nbits, 8)
+ if remainder != 0:
+ # In the future, we might support arbitrary bit lengths, but for now we don't.
+ raise ValueError("nbits must be a multiple of 8; got %d" % (nbits,))
+ if nbytes < 1:
+ raise ValueError("nbits too small")
+ elif nbytes > 0xffff:
+ raise ValueError("nbits too large")
+
+ initval = _encode(initial_value, nbytes, little_endian)
+
+ if little_endian:
+ return _counter._newLE(bstr(prefix), bstr(suffix), initval, allow_wraparound=allow_wraparound, disable_shortcut=disable_shortcut)
+ else:
+ return _counter._newBE(bstr(prefix), bstr(suffix), initval, allow_wraparound=allow_wraparound, disable_shortcut=disable_shortcut)
+
+def _encode(n, nbytes, little_endian=False):
+ retval = []
+ n = long(n)
+ for i in range(nbytes):
+ if little_endian:
+ retval.append(bchr(n & 0xff))
+ else:
+ retval.insert(0, bchr(n & 0xff))
+ n >>= 8
+ return b("").join(retval)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Util/RFC1751.py b/lib/Python/Lib/Crypto/Util/RFC1751.py
new file mode 100644
index 000000000..9786e6f59
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/RFC1751.py
@@ -0,0 +1,364 @@
+# rfc1751.py : Converts between 128-bit strings and a human-readable
+# sequence of words, as defined in RFC1751: "A Convention for
+# Human-Readable 128-bit Keys", by Daniel L. McDonald.
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew M. Kuchling and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+
+import binascii
+from Crypto.Util.py3compat import *
+
+binary={0:'0000', 1:'0001', 2:'0010', 3:'0011', 4:'0100', 5:'0101',
+ 6:'0110', 7:'0111', 8:'1000', 9:'1001', 10:'1010', 11:'1011',
+ 12:'1100', 13:'1101', 14:'1110', 15:'1111'}
+
+def _key2bin(s):
+ "Convert a key into a string of binary digits"
+ kl=map(lambda x: bord(x), s)
+ kl=map(lambda x: binary[x>>4]+binary[x&15], kl)
+ return ''.join(kl)
+
+def _extract(key, start, length):
+ """Extract a bitstring(2.x)/bytestring(2.x) from a string of binary digits, and return its
+ numeric value."""
+ k=key[start:start+length]
+ return reduce(lambda x,y: x*2+ord(y)-48, k, 0)
+
+def key_to_english (key):
+ """key_to_english(key:string(2.x)/bytes(3.x)) : string
+ Transform an arbitrary key into a string containing English words.
+ The key length must be a multiple of 8.
+ """
+ english=''
+ for index in range(0, len(key), 8): # Loop over 8-byte subkeys
+ subkey=key[index:index+8]
+ # Compute the parity of the key
+ skbin=_key2bin(subkey) ; p=0
+ for i in range(0, 64, 2): p=p+_extract(skbin, i, 2)
+ # Append parity bits to the subkey
+ skbin=_key2bin(subkey+bchr((p<<6) & 255))
+ for i in range(0, 64, 11):
+ english=english+wordlist[_extract(skbin, i, 11)]+' '
+
+ return english[:-1] # Remove the trailing space
+
+def english_to_key (s):
+ """english_to_key(string):string(2.x)/bytes(2.x)
+ Transform a string into a corresponding key.
+ The string must contain words separated by whitespace; the number
+ of words must be a multiple of 6.
+ """
+
+ L=s.upper().split() ; key=b('')
+ for index in range(0, len(L), 6):
+ sublist=L[index:index+6] ; char=9*[0] ; bits=0
+ for i in sublist:
+ index = wordlist.index(i)
+ shift = (8-(bits+11)%8) %8
+ y = index << shift
+ cl, cc, cr = (y>>16), (y>>8)&0xff, y & 0xff
+ if (shift>5):
+ char[bits>>3] = char[bits>>3] | cl
+ char[(bits>>3)+1] = char[(bits>>3)+1] | cc
+ char[(bits>>3)+2] = char[(bits>>3)+2] | cr
+ elif shift>-3:
+ char[bits>>3] = char[bits>>3] | cc
+ char[(bits>>3)+1] = char[(bits>>3)+1] | cr
+ else: char[bits>>3] = char[bits>>3] | cr
+ bits=bits+11
+ subkey=reduce(lambda x,y:x+bchr(y), char, b(''))
+
+ # Check the parity of the resulting key
+ skbin=_key2bin(subkey)
+ p=0
+ for i in range(0, 64, 2): p=p+_extract(skbin, i, 2)
+ if (p&3) != _extract(skbin, 64, 2):
+ raise ValueError, "Parity error in resulting key"
+ key=key+subkey[0:8]
+ return key
+
+wordlist=[ "A", "ABE", "ACE", "ACT", "AD", "ADA", "ADD",
+ "AGO", "AID", "AIM", "AIR", "ALL", "ALP", "AM", "AMY", "AN", "ANA",
+ "AND", "ANN", "ANT", "ANY", "APE", "APS", "APT", "ARC", "ARE", "ARK",
+ "ARM", "ART", "AS", "ASH", "ASK", "AT", "ATE", "AUG", "AUK", "AVE",
+ "AWE", "AWK", "AWL", "AWN", "AX", "AYE", "BAD", "BAG", "BAH", "BAM",
+ "BAN", "BAR", "BAT", "BAY", "BE", "BED", "BEE", "BEG", "BEN", "BET",
+ "BEY", "BIB", "BID", "BIG", "BIN", "BIT", "BOB", "BOG", "BON", "BOO",
+ "BOP", "BOW", "BOY", "BUB", "BUD", "BUG", "BUM", "BUN", "BUS", "BUT",
+ "BUY", "BY", "BYE", "CAB", "CAL", "CAM", "CAN", "CAP", "CAR", "CAT",
+ "CAW", "COD", "COG", "COL", "CON", "COO", "COP", "COT", "COW", "COY",
+ "CRY", "CUB", "CUE", "CUP", "CUR", "CUT", "DAB", "DAD", "DAM", "DAN",
+ "DAR", "DAY", "DEE", "DEL", "DEN", "DES", "DEW", "DID", "DIE", "DIG",
+ "DIN", "DIP", "DO", "DOE", "DOG", "DON", "DOT", "DOW", "DRY", "DUB",
+ "DUD", "DUE", "DUG", "DUN", "EAR", "EAT", "ED", "EEL", "EGG", "EGO",
+ "ELI", "ELK", "ELM", "ELY", "EM", "END", "EST", "ETC", "EVA", "EVE",
+ "EWE", "EYE", "FAD", "FAN", "FAR", "FAT", "FAY", "FED", "FEE", "FEW",
+ "FIB", "FIG", "FIN", "FIR", "FIT", "FLO", "FLY", "FOE", "FOG", "FOR",
+ "FRY", "FUM", "FUN", "FUR", "GAB", "GAD", "GAG", "GAL", "GAM", "GAP",
+ "GAS", "GAY", "GEE", "GEL", "GEM", "GET", "GIG", "GIL", "GIN", "GO",
+ "GOT", "GUM", "GUN", "GUS", "GUT", "GUY", "GYM", "GYP", "HA", "HAD",
+ "HAL", "HAM", "HAN", "HAP", "HAS", "HAT", "HAW", "HAY", "HE", "HEM",
+ "HEN", "HER", "HEW", "HEY", "HI", "HID", "HIM", "HIP", "HIS", "HIT",
+ "HO", "HOB", "HOC", "HOE", "HOG", "HOP", "HOT", "HOW", "HUB", "HUE",
+ "HUG", "HUH", "HUM", "HUT", "I", "ICY", "IDA", "IF", "IKE", "ILL",
+ "INK", "INN", "IO", "ION", "IQ", "IRA", "IRE", "IRK", "IS", "IT",
+ "ITS", "IVY", "JAB", "JAG", "JAM", "JAN", "JAR", "JAW", "JAY", "JET",
+ "JIG", "JIM", "JO", "JOB", "JOE", "JOG", "JOT", "JOY", "JUG", "JUT",
+ "KAY", "KEG", "KEN", "KEY", "KID", "KIM", "KIN", "KIT", "LA", "LAB",
+ "LAC", "LAD", "LAG", "LAM", "LAP", "LAW", "LAY", "LEA", "LED", "LEE",
+ "LEG", "LEN", "LEO", "LET", "LEW", "LID", "LIE", "LIN", "LIP", "LIT",
+ "LO", "LOB", "LOG", "LOP", "LOS", "LOT", "LOU", "LOW", "LOY", "LUG",
+ "LYE", "MA", "MAC", "MAD", "MAE", "MAN", "MAO", "MAP", "MAT", "MAW",
+ "MAY", "ME", "MEG", "MEL", "MEN", "MET", "MEW", "MID", "MIN", "MIT",
+ "MOB", "MOD", "MOE", "MOO", "MOP", "MOS", "MOT", "MOW", "MUD", "MUG",
+ "MUM", "MY", "NAB", "NAG", "NAN", "NAP", "NAT", "NAY", "NE", "NED",
+ "NEE", "NET", "NEW", "NIB", "NIL", "NIP", "NIT", "NO", "NOB", "NOD",
+ "NON", "NOR", "NOT", "NOV", "NOW", "NU", "NUN", "NUT", "O", "OAF",
+ "OAK", "OAR", "OAT", "ODD", "ODE", "OF", "OFF", "OFT", "OH", "OIL",
+ "OK", "OLD", "ON", "ONE", "OR", "ORB", "ORE", "ORR", "OS", "OTT",
+ "OUR", "OUT", "OVA", "OW", "OWE", "OWL", "OWN", "OX", "PA", "PAD",
+ "PAL", "PAM", "PAN", "PAP", "PAR", "PAT", "PAW", "PAY", "PEA", "PEG",
+ "PEN", "PEP", "PER", "PET", "PEW", "PHI", "PI", "PIE", "PIN", "PIT",
+ "PLY", "PO", "POD", "POE", "POP", "POT", "POW", "PRO", "PRY", "PUB",
+ "PUG", "PUN", "PUP", "PUT", "QUO", "RAG", "RAM", "RAN", "RAP", "RAT",
+ "RAW", "RAY", "REB", "RED", "REP", "RET", "RIB", "RID", "RIG", "RIM",
+ "RIO", "RIP", "ROB", "ROD", "ROE", "RON", "ROT", "ROW", "ROY", "RUB",
+ "RUE", "RUG", "RUM", "RUN", "RYE", "SAC", "SAD", "SAG", "SAL", "SAM",
+ "SAN", "SAP", "SAT", "SAW", "SAY", "SEA", "SEC", "SEE", "SEN", "SET",
+ "SEW", "SHE", "SHY", "SIN", "SIP", "SIR", "SIS", "SIT", "SKI", "SKY",
+ "SLY", "SO", "SOB", "SOD", "SON", "SOP", "SOW", "SOY", "SPA", "SPY",
+ "SUB", "SUD", "SUE", "SUM", "SUN", "SUP", "TAB", "TAD", "TAG", "TAN",
+ "TAP", "TAR", "TEA", "TED", "TEE", "TEN", "THE", "THY", "TIC", "TIE",
+ "TIM", "TIN", "TIP", "TO", "TOE", "TOG", "TOM", "TON", "TOO", "TOP",
+ "TOW", "TOY", "TRY", "TUB", "TUG", "TUM", "TUN", "TWO", "UN", "UP",
+ "US", "USE", "VAN", "VAT", "VET", "VIE", "WAD", "WAG", "WAR", "WAS",
+ "WAY", "WE", "WEB", "WED", "WEE", "WET", "WHO", "WHY", "WIN", "WIT",
+ "WOK", "WON", "WOO", "WOW", "WRY", "WU", "YAM", "YAP", "YAW", "YE",
+ "YEA", "YES", "YET", "YOU", "ABED", "ABEL", "ABET", "ABLE", "ABUT",
+ "ACHE", "ACID", "ACME", "ACRE", "ACTA", "ACTS", "ADAM", "ADDS",
+ "ADEN", "AFAR", "AFRO", "AGEE", "AHEM", "AHOY", "AIDA", "AIDE",
+ "AIDS", "AIRY", "AJAR", "AKIN", "ALAN", "ALEC", "ALGA", "ALIA",
+ "ALLY", "ALMA", "ALOE", "ALSO", "ALTO", "ALUM", "ALVA", "AMEN",
+ "AMES", "AMID", "AMMO", "AMOK", "AMOS", "AMRA", "ANDY", "ANEW",
+ "ANNA", "ANNE", "ANTE", "ANTI", "AQUA", "ARAB", "ARCH", "AREA",
+ "ARGO", "ARID", "ARMY", "ARTS", "ARTY", "ASIA", "ASKS", "ATOM",
+ "AUNT", "AURA", "AUTO", "AVER", "AVID", "AVIS", "AVON", "AVOW",
+ "AWAY", "AWRY", "BABE", "BABY", "BACH", "BACK", "BADE", "BAIL",
+ "BAIT", "BAKE", "BALD", "BALE", "BALI", "BALK", "BALL", "BALM",
+ "BAND", "BANE", "BANG", "BANK", "BARB", "BARD", "BARE", "BARK",
+ "BARN", "BARR", "BASE", "BASH", "BASK", "BASS", "BATE", "BATH",
+ "BAWD", "BAWL", "BEAD", "BEAK", "BEAM", "BEAN", "BEAR", "BEAT",
+ "BEAU", "BECK", "BEEF", "BEEN", "BEER",
+ "BEET", "BELA", "BELL", "BELT", "BEND", "BENT", "BERG", "BERN",
+ "BERT", "BESS", "BEST", "BETA", "BETH", "BHOY", "BIAS", "BIDE",
+ "BIEN", "BILE", "BILK", "BILL", "BIND", "BING", "BIRD", "BITE",
+ "BITS", "BLAB", "BLAT", "BLED", "BLEW", "BLOB", "BLOC", "BLOT",
+ "BLOW", "BLUE", "BLUM", "BLUR", "BOAR", "BOAT", "BOCA", "BOCK",
+ "BODE", "BODY", "BOGY", "BOHR", "BOIL", "BOLD", "BOLO", "BOLT",
+ "BOMB", "BONA", "BOND", "BONE", "BONG", "BONN", "BONY", "BOOK",
+ "BOOM", "BOON", "BOOT", "BORE", "BORG", "BORN", "BOSE", "BOSS",
+ "BOTH", "BOUT", "BOWL", "BOYD", "BRAD", "BRAE", "BRAG", "BRAN",
+ "BRAY", "BRED", "BREW", "BRIG", "BRIM", "BROW", "BUCK", "BUDD",
+ "BUFF", "BULB", "BULK", "BULL", "BUNK", "BUNT", "BUOY", "BURG",
+ "BURL", "BURN", "BURR", "BURT", "BURY", "BUSH", "BUSS", "BUST",
+ "BUSY", "BYTE", "CADY", "CAFE", "CAGE", "CAIN", "CAKE", "CALF",
+ "CALL", "CALM", "CAME", "CANE", "CANT", "CARD", "CARE", "CARL",
+ "CARR", "CART", "CASE", "CASH", "CASK", "CAST", "CAVE", "CEIL",
+ "CELL", "CENT", "CERN", "CHAD", "CHAR", "CHAT", "CHAW", "CHEF",
+ "CHEN", "CHEW", "CHIC", "CHIN", "CHOU", "CHOW", "CHUB", "CHUG",
+ "CHUM", "CITE", "CITY", "CLAD", "CLAM", "CLAN", "CLAW", "CLAY",
+ "CLOD", "CLOG", "CLOT", "CLUB", "CLUE", "COAL", "COAT", "COCA",
+ "COCK", "COCO", "CODA", "CODE", "CODY", "COED", "COIL", "COIN",
+ "COKE", "COLA", "COLD", "COLT", "COMA", "COMB", "COME", "COOK",
+ "COOL", "COON", "COOT", "CORD", "CORE", "CORK", "CORN", "COST",
+ "COVE", "COWL", "CRAB", "CRAG", "CRAM", "CRAY", "CREW", "CRIB",
+ "CROW", "CRUD", "CUBA", "CUBE", "CUFF", "CULL", "CULT", "CUNY",
+ "CURB", "CURD", "CURE", "CURL", "CURT", "CUTS", "DADE", "DALE",
+ "DAME", "DANA", "DANE", "DANG", "DANK", "DARE", "DARK", "DARN",
+ "DART", "DASH", "DATA", "DATE", "DAVE", "DAVY", "DAWN", "DAYS",
+ "DEAD", "DEAF", "DEAL", "DEAN", "DEAR", "DEBT", "DECK", "DEED",
+ "DEEM", "DEER", "DEFT", "DEFY", "DELL", "DENT", "DENY", "DESK",
+ "DIAL", "DICE", "DIED", "DIET", "DIME", "DINE", "DING", "DINT",
+ "DIRE", "DIRT", "DISC", "DISH", "DISK", "DIVE", "DOCK", "DOES",
+ "DOLE", "DOLL", "DOLT", "DOME", "DONE", "DOOM", "DOOR", "DORA",
+ "DOSE", "DOTE", "DOUG", "DOUR", "DOVE", "DOWN", "DRAB", "DRAG",
+ "DRAM", "DRAW", "DREW", "DRUB", "DRUG", "DRUM", "DUAL", "DUCK",
+ "DUCT", "DUEL", "DUET", "DUKE", "DULL", "DUMB", "DUNE", "DUNK",
+ "DUSK", "DUST", "DUTY", "EACH", "EARL", "EARN", "EASE", "EAST",
+ "EASY", "EBEN", "ECHO", "EDDY", "EDEN", "EDGE", "EDGY", "EDIT",
+ "EDNA", "EGAN", "ELAN", "ELBA", "ELLA", "ELSE", "EMIL", "EMIT",
+ "EMMA", "ENDS", "ERIC", "EROS", "EVEN", "EVER", "EVIL", "EYED",
+ "FACE", "FACT", "FADE", "FAIL", "FAIN", "FAIR", "FAKE", "FALL",
+ "FAME", "FANG", "FARM", "FAST", "FATE", "FAWN", "FEAR", "FEAT",
+ "FEED", "FEEL", "FEET", "FELL", "FELT", "FEND", "FERN", "FEST",
+ "FEUD", "FIEF", "FIGS", "FILE", "FILL", "FILM", "FIND", "FINE",
+ "FINK", "FIRE", "FIRM", "FISH", "FISK", "FIST", "FITS", "FIVE",
+ "FLAG", "FLAK", "FLAM", "FLAT", "FLAW", "FLEA", "FLED", "FLEW",
+ "FLIT", "FLOC", "FLOG", "FLOW", "FLUB", "FLUE", "FOAL", "FOAM",
+ "FOGY", "FOIL", "FOLD", "FOLK", "FOND", "FONT", "FOOD", "FOOL",
+ "FOOT", "FORD", "FORE", "FORK", "FORM", "FORT", "FOSS", "FOUL",
+ "FOUR", "FOWL", "FRAU", "FRAY", "FRED", "FREE", "FRET", "FREY",
+ "FROG", "FROM", "FUEL", "FULL", "FUME", "FUND", "FUNK", "FURY",
+ "FUSE", "FUSS", "GAFF", "GAGE", "GAIL", "GAIN", "GAIT", "GALA",
+ "GALE", "GALL", "GALT", "GAME", "GANG", "GARB", "GARY", "GASH",
+ "GATE", "GAUL", "GAUR", "GAVE", "GAWK", "GEAR", "GELD", "GENE",
+ "GENT", "GERM", "GETS", "GIBE", "GIFT", "GILD", "GILL", "GILT",
+ "GINA", "GIRD", "GIRL", "GIST", "GIVE", "GLAD", "GLEE", "GLEN",
+ "GLIB", "GLOB", "GLOM", "GLOW", "GLUE", "GLUM", "GLUT", "GOAD",
+ "GOAL", "GOAT", "GOER", "GOES", "GOLD", "GOLF", "GONE", "GONG",
+ "GOOD", "GOOF", "GORE", "GORY", "GOSH", "GOUT", "GOWN", "GRAB",
+ "GRAD", "GRAY", "GREG", "GREW", "GREY", "GRID", "GRIM", "GRIN",
+ "GRIT", "GROW", "GRUB", "GULF", "GULL", "GUNK", "GURU", "GUSH",
+ "GUST", "GWEN", "GWYN", "HAAG", "HAAS", "HACK", "HAIL", "HAIR",
+ "HALE", "HALF", "HALL", "HALO", "HALT", "HAND", "HANG", "HANK",
+ "HANS", "HARD", "HARK", "HARM", "HART", "HASH", "HAST", "HATE",
+ "HATH", "HAUL", "HAVE", "HAWK", "HAYS", "HEAD", "HEAL", "HEAR",
+ "HEAT", "HEBE", "HECK", "HEED", "HEEL", "HEFT", "HELD", "HELL",
+ "HELM", "HERB", "HERD", "HERE", "HERO", "HERS", "HESS", "HEWN",
+ "HICK", "HIDE", "HIGH", "HIKE", "HILL", "HILT", "HIND", "HINT",
+ "HIRE", "HISS", "HIVE", "HOBO", "HOCK", "HOFF", "HOLD", "HOLE",
+ "HOLM", "HOLT", "HOME", "HONE", "HONK", "HOOD", "HOOF", "HOOK",
+ "HOOT", "HORN", "HOSE", "HOST", "HOUR", "HOVE", "HOWE", "HOWL",
+ "HOYT", "HUCK", "HUED", "HUFF", "HUGE", "HUGH", "HUGO", "HULK",
+ "HULL", "HUNK", "HUNT", "HURD", "HURL", "HURT", "HUSH", "HYDE",
+ "HYMN", "IBIS", "ICON", "IDEA", "IDLE", "IFFY", "INCA", "INCH",
+ "INTO", "IONS", "IOTA", "IOWA", "IRIS", "IRMA", "IRON", "ISLE",
+ "ITCH", "ITEM", "IVAN", "JACK", "JADE", "JAIL", "JAKE", "JANE",
+ "JAVA", "JEAN", "JEFF", "JERK", "JESS", "JEST", "JIBE", "JILL",
+ "JILT", "JIVE", "JOAN", "JOBS", "JOCK", "JOEL", "JOEY", "JOHN",
+ "JOIN", "JOKE", "JOLT", "JOVE", "JUDD", "JUDE", "JUDO", "JUDY",
+ "JUJU", "JUKE", "JULY", "JUNE", "JUNK", "JUNO", "JURY", "JUST",
+ "JUTE", "KAHN", "KALE", "KANE", "KANT", "KARL", "KATE", "KEEL",
+ "KEEN", "KENO", "KENT", "KERN", "KERR", "KEYS", "KICK", "KILL",
+ "KIND", "KING", "KIRK", "KISS", "KITE", "KLAN", "KNEE", "KNEW",
+ "KNIT", "KNOB", "KNOT", "KNOW", "KOCH", "KONG", "KUDO", "KURD",
+ "KURT", "KYLE", "LACE", "LACK", "LACY", "LADY", "LAID", "LAIN",
+ "LAIR", "LAKE", "LAMB", "LAME", "LAND", "LANE", "LANG", "LARD",
+ "LARK", "LASS", "LAST", "LATE", "LAUD", "LAVA", "LAWN", "LAWS",
+ "LAYS", "LEAD", "LEAF", "LEAK", "LEAN", "LEAR", "LEEK", "LEER",
+ "LEFT", "LEND", "LENS", "LENT", "LEON", "LESK", "LESS", "LEST",
+ "LETS", "LIAR", "LICE", "LICK", "LIED", "LIEN", "LIES", "LIEU",
+ "LIFE", "LIFT", "LIKE", "LILA", "LILT", "LILY", "LIMA", "LIMB",
+ "LIME", "LIND", "LINE", "LINK", "LINT", "LION", "LISA", "LIST",
+ "LIVE", "LOAD", "LOAF", "LOAM", "LOAN", "LOCK", "LOFT", "LOGE",
+ "LOIS", "LOLA", "LONE", "LONG", "LOOK", "LOON", "LOOT", "LORD",
+ "LORE", "LOSE", "LOSS", "LOST", "LOUD", "LOVE", "LOWE", "LUCK",
+ "LUCY", "LUGE", "LUKE", "LULU", "LUND", "LUNG", "LURA", "LURE",
+ "LURK", "LUSH", "LUST", "LYLE", "LYNN", "LYON", "LYRA", "MACE",
+ "MADE", "MAGI", "MAID", "MAIL", "MAIN", "MAKE", "MALE", "MALI",
+ "MALL", "MALT", "MANA", "MANN", "MANY", "MARC", "MARE", "MARK",
+ "MARS", "MART", "MARY", "MASH", "MASK", "MASS", "MAST", "MATE",
+ "MATH", "MAUL", "MAYO", "MEAD", "MEAL", "MEAN", "MEAT", "MEEK",
+ "MEET", "MELD", "MELT", "MEMO", "MEND", "MENU", "MERT", "MESH",
+ "MESS", "MICE", "MIKE", "MILD", "MILE", "MILK", "MILL", "MILT",
+ "MIMI", "MIND", "MINE", "MINI", "MINK", "MINT", "MIRE", "MISS",
+ "MIST", "MITE", "MITT", "MOAN", "MOAT", "MOCK", "MODE", "MOLD",
+ "MOLE", "MOLL", "MOLT", "MONA", "MONK", "MONT", "MOOD", "MOON",
+ "MOOR", "MOOT", "MORE", "MORN", "MORT", "MOSS", "MOST", "MOTH",
+ "MOVE", "MUCH", "MUCK", "MUDD", "MUFF", "MULE", "MULL", "MURK",
+ "MUSH", "MUST", "MUTE", "MUTT", "MYRA", "MYTH", "NAGY", "NAIL",
+ "NAIR", "NAME", "NARY", "NASH", "NAVE", "NAVY", "NEAL", "NEAR",
+ "NEAT", "NECK", "NEED", "NEIL", "NELL", "NEON", "NERO", "NESS",
+ "NEST", "NEWS", "NEWT", "NIBS", "NICE", "NICK", "NILE", "NINA",
+ "NINE", "NOAH", "NODE", "NOEL", "NOLL", "NONE", "NOOK", "NOON",
+ "NORM", "NOSE", "NOTE", "NOUN", "NOVA", "NUDE", "NULL", "NUMB",
+ "OATH", "OBEY", "OBOE", "ODIN", "OHIO", "OILY", "OINT", "OKAY",
+ "OLAF", "OLDY", "OLGA", "OLIN", "OMAN", "OMEN", "OMIT", "ONCE",
+ "ONES", "ONLY", "ONTO", "ONUS", "ORAL", "ORGY", "OSLO", "OTIS",
+ "OTTO", "OUCH", "OUST", "OUTS", "OVAL", "OVEN", "OVER", "OWLY",
+ "OWNS", "QUAD", "QUIT", "QUOD", "RACE", "RACK", "RACY", "RAFT",
+ "RAGE", "RAID", "RAIL", "RAIN", "RAKE", "RANK", "RANT", "RARE",
+ "RASH", "RATE", "RAVE", "RAYS", "READ", "REAL", "REAM", "REAR",
+ "RECK", "REED", "REEF", "REEK", "REEL", "REID", "REIN", "RENA",
+ "REND", "RENT", "REST", "RICE", "RICH", "RICK", "RIDE", "RIFT",
+ "RILL", "RIME", "RING", "RINK", "RISE", "RISK", "RITE", "ROAD",
+ "ROAM", "ROAR", "ROBE", "ROCK", "RODE", "ROIL", "ROLL", "ROME",
+ "ROOD", "ROOF", "ROOK", "ROOM", "ROOT", "ROSA", "ROSE", "ROSS",
+ "ROSY", "ROTH", "ROUT", "ROVE", "ROWE", "ROWS", "RUBE", "RUBY",
+ "RUDE", "RUDY", "RUIN", "RULE", "RUNG", "RUNS", "RUNT", "RUSE",
+ "RUSH", "RUSK", "RUSS", "RUST", "RUTH", "SACK", "SAFE", "SAGE",
+ "SAID", "SAIL", "SALE", "SALK", "SALT", "SAME", "SAND", "SANE",
+ "SANG", "SANK", "SARA", "SAUL", "SAVE", "SAYS", "SCAN", "SCAR",
+ "SCAT", "SCOT", "SEAL", "SEAM", "SEAR", "SEAT", "SEED", "SEEK",
+ "SEEM", "SEEN", "SEES", "SELF", "SELL", "SEND", "SENT", "SETS",
+ "SEWN", "SHAG", "SHAM", "SHAW", "SHAY", "SHED", "SHIM", "SHIN",
+ "SHOD", "SHOE", "SHOT", "SHOW", "SHUN", "SHUT", "SICK", "SIDE",
+ "SIFT", "SIGH", "SIGN", "SILK", "SILL", "SILO", "SILT", "SINE",
+ "SING", "SINK", "SIRE", "SITE", "SITS", "SITU", "SKAT", "SKEW",
+ "SKID", "SKIM", "SKIN", "SKIT", "SLAB", "SLAM", "SLAT", "SLAY",
+ "SLED", "SLEW", "SLID", "SLIM", "SLIT", "SLOB", "SLOG", "SLOT",
+ "SLOW", "SLUG", "SLUM", "SLUR", "SMOG", "SMUG", "SNAG", "SNOB",
+ "SNOW", "SNUB", "SNUG", "SOAK", "SOAR", "SOCK", "SODA", "SOFA",
+ "SOFT", "SOIL", "SOLD", "SOME", "SONG", "SOON", "SOOT", "SORE",
+ "SORT", "SOUL", "SOUR", "SOWN", "STAB", "STAG", "STAN", "STAR",
+ "STAY", "STEM", "STEW", "STIR", "STOW", "STUB", "STUN", "SUCH",
+ "SUDS", "SUIT", "SULK", "SUMS", "SUNG", "SUNK", "SURE", "SURF",
+ "SWAB", "SWAG", "SWAM", "SWAN", "SWAT", "SWAY", "SWIM", "SWUM",
+ "TACK", "TACT", "TAIL", "TAKE", "TALE", "TALK", "TALL", "TANK",
+ "TASK", "TATE", "TAUT", "TEAL", "TEAM", "TEAR", "TECH", "TEEM",
+ "TEEN", "TEET", "TELL", "TEND", "TENT", "TERM", "TERN", "TESS",
+ "TEST", "THAN", "THAT", "THEE", "THEM", "THEN", "THEY", "THIN",
+ "THIS", "THUD", "THUG", "TICK", "TIDE", "TIDY", "TIED", "TIER",
+ "TILE", "TILL", "TILT", "TIME", "TINA", "TINE", "TINT", "TINY",
+ "TIRE", "TOAD", "TOGO", "TOIL", "TOLD", "TOLL", "TONE", "TONG",
+ "TONY", "TOOK", "TOOL", "TOOT", "TORE", "TORN", "TOTE", "TOUR",
+ "TOUT", "TOWN", "TRAG", "TRAM", "TRAY", "TREE", "TREK", "TRIG",
+ "TRIM", "TRIO", "TROD", "TROT", "TROY", "TRUE", "TUBA", "TUBE",
+ "TUCK", "TUFT", "TUNA", "TUNE", "TUNG", "TURF", "TURN", "TUSK",
+ "TWIG", "TWIN", "TWIT", "ULAN", "UNIT", "URGE", "USED", "USER",
+ "USES", "UTAH", "VAIL", "VAIN", "VALE", "VARY", "VASE", "VAST",
+ "VEAL", "VEDA", "VEIL", "VEIN", "VEND", "VENT", "VERB", "VERY",
+ "VETO", "VICE", "VIEW", "VINE", "VISE", "VOID", "VOLT", "VOTE",
+ "WACK", "WADE", "WAGE", "WAIL", "WAIT", "WAKE", "WALE", "WALK",
+ "WALL", "WALT", "WAND", "WANE", "WANG", "WANT", "WARD", "WARM",
+ "WARN", "WART", "WASH", "WAST", "WATS", "WATT", "WAVE", "WAVY",
+ "WAYS", "WEAK", "WEAL", "WEAN", "WEAR", "WEED", "WEEK", "WEIR",
+ "WELD", "WELL", "WELT", "WENT", "WERE", "WERT", "WEST", "WHAM",
+ "WHAT", "WHEE", "WHEN", "WHET", "WHOA", "WHOM", "WICK", "WIFE",
+ "WILD", "WILL", "WIND", "WINE", "WING", "WINK", "WINO", "WIRE",
+ "WISE", "WISH", "WITH", "WOLF", "WONT", "WOOD", "WOOL", "WORD",
+ "WORE", "WORK", "WORM", "WORN", "WOVE", "WRIT", "WYNN", "YALE",
+ "YANG", "YANK", "YARD", "YARN", "YAWL", "YAWN", "YEAH", "YEAR",
+ "YELL", "YOGA", "YOKE" ]
+
+if __name__=='__main__':
+ data = [('EB33F77EE73D4053', 'TIDE ITCH SLOW REIN RULE MOT'),
+ ('CCAC2AED591056BE4F90FD441C534766',
+ 'RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE'),
+ ('EFF81F9BFBC65350920CDD7416DE8009',
+ 'TROD MUTE TAIL WARM CHAR KONG HAAG CITY BORE O TEAL AWL')
+ ]
+
+ for key, words in data:
+ print 'Trying key', key
+ key=binascii.a2b_hex(key)
+ w2=key_to_english(key)
+ if w2!=words:
+ print 'key_to_english fails on key', repr(key), ', producing', str(w2)
+ k2=english_to_key(words)
+ if k2!=key:
+ print 'english_to_key fails on key', repr(key), ', producing', repr(k2)
+
+
diff --git a/lib/Python/Lib/Crypto/Util/__init__.py b/lib/Python/Lib/Crypto/Util/__init__.py
new file mode 100644
index 000000000..a3bef8aea
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/__init__.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Miscellaneous modules
+
+Contains useful modules that don't belong into any of the
+other Crypto.* subpackages.
+
+Crypto.Util.number Number-theoretic functions (primality testing, etc.)
+Crypto.Util.randpool Random number generation
+Crypto.Util.RFC1751 Converts between 128-bit keys and human-readable
+ strings of words.
+Crypto.Util.asn1 Minimal support for ASN.1 DER encoding
+
+"""
+
+__all__ = ['randpool', 'RFC1751', 'number', 'strxor', 'asn1' ]
+
+__revision__ = "$Id$"
+
diff --git a/lib/Python/Lib/Crypto/Util/_number_new.py b/lib/Python/Lib/Crypto/Util/_number_new.py
new file mode 100644
index 000000000..b040025f3
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/_number_new.py
@@ -0,0 +1,119 @@
+# -*- coding: ascii -*-
+#
+# Util/_number_new.py : utility functions
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+## NOTE: Do not import this module directly. Import these functions from Crypto.Util.number.
+
+__revision__ = "$Id$"
+__all__ = ['ceil_shift', 'ceil_div', 'floor_div', 'exact_log2', 'exact_div']
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] == 1:
+ from Crypto.Util.py21compat import *
+
+def ceil_shift(n, b):
+ """Return ceil(n / 2**b) without performing any floating-point or division operations.
+
+ This is done by right-shifting n by b bits and incrementing the result by 1
+ if any '1' bits were shifted out.
+ """
+ if not isinstance(n, (int, long)) or not isinstance(b, (int, long)):
+ raise TypeError("unsupported operand type(s): %r and %r" % (type(n).__name__, type(b).__name__))
+
+ assert n >= 0 and b >= 0 # I haven't tested or even thought about negative values
+ mask = (1L << b) - 1
+ if n & mask:
+ return (n >> b) + 1
+ else:
+ return n >> b
+
+def ceil_div(a, b):
+ """Return ceil(a / b) without performing any floating-point operations."""
+
+ if not isinstance(a, (int, long)) or not isinstance(b, (int, long)):
+ raise TypeError("unsupported operand type(s): %r and %r" % (type(a).__name__, type(b).__name__))
+
+ (q, r) = divmod(a, b)
+ if r:
+ return q + 1
+ else:
+ return q
+
+def floor_div(a, b):
+ if not isinstance(a, (int, long)) or not isinstance(b, (int, long)):
+ raise TypeError("unsupported operand type(s): %r and %r" % (type(a).__name__, type(b).__name__))
+
+ (q, r) = divmod(a, b)
+ return q
+
+def exact_log2(num):
+ """Find and return an integer i >= 0 such that num == 2**i.
+
+ If no such integer exists, this function raises ValueError.
+ """
+
+ if not isinstance(num, (int, long)):
+ raise TypeError("unsupported operand type: %r" % (type(num).__name__,))
+
+ n = long(num)
+ if n <= 0:
+ raise ValueError("cannot compute logarithm of non-positive number")
+
+ i = 0
+ while n != 0:
+ if (n & 1) and n != 1:
+ raise ValueError("No solution could be found")
+ i += 1
+ n >>= 1
+ i -= 1
+
+ assert num == (1L << i)
+ return i
+
+def exact_div(p, d, allow_divzero=False):
+ """Find and return an integer n such that p == n * d
+
+ If no such integer exists, this function raises ValueError.
+
+ Both operands must be integers.
+
+ If the second operand is zero, this function will raise ZeroDivisionError
+ unless allow_divzero is true (default: False).
+ """
+
+ if not isinstance(p, (int, long)) or not isinstance(d, (int, long)):
+ raise TypeError("unsupported operand type(s): %r and %r" % (type(p).__name__, type(d).__name__))
+
+ if d == 0 and allow_divzero:
+ n = 0
+ if p != n * d:
+ raise ValueError("No solution could be found")
+ else:
+ (n, r) = divmod(p, d)
+ if r != 0:
+ raise ValueError("No solution could be found")
+
+ assert p == n * d
+ return n
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Util/asn1.py b/lib/Python/Lib/Crypto/Util/asn1.py
new file mode 100644
index 000000000..dd5ec31d4
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/asn1.py
@@ -0,0 +1,286 @@
+# -*- coding: ascii -*-
+#
+# Util/asn1.py : Minimal support for ASN.1 DER binary encoding.
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+from Crypto.Util.number import long_to_bytes, bytes_to_long
+import sys
+from Crypto.Util.py3compat import *
+
+__all__ = [ 'DerObject', 'DerInteger', 'DerOctetString', 'DerNull', 'DerSequence', 'DerObjectId' ]
+
+class DerObject:
+ """Base class for defining a single DER object.
+
+ Instantiate this class ONLY when you have to decode a DER element.
+ """
+
+ # Known TAG types
+ typeTags = { 'SEQUENCE': 0x30, 'BIT STRING': 0x03, 'INTEGER': 0x02,
+ 'OCTET STRING': 0x04, 'NULL': 0x05, 'OBJECT IDENTIFIER': 0x06 }
+
+ def __init__(self, ASN1Type=None, payload=b('')):
+ """Initialize the DER object according to a specific type.
+
+ The ASN.1 type is either specified as the ASN.1 string (e.g.
+ 'SEQUENCE'), directly with its numerical tag or with no tag
+ at all (None)."""
+ if isInt(ASN1Type) or ASN1Type is None:
+ self.typeTag = ASN1Type
+ else:
+ if len(ASN1Type)==1:
+ self.typeTag = ord(ASN1Type)
+ else:
+ self.typeTag = self.typeTags.get(ASN1Type)
+ self.payload = payload
+
+ def isType(self, ASN1Type):
+ return self.typeTags[ASN1Type]==self.typeTag
+
+ def _lengthOctets(self, payloadLen):
+ """Return a byte string that encodes the given payload length (in
+ bytes) in a format suitable for a DER length tag (L).
+ """
+ if payloadLen>127:
+ encoding = long_to_bytes(payloadLen)
+ return bchr(len(encoding)+128) + encoding
+ return bchr(payloadLen)
+
+ def encode(self):
+ """Return a complete DER element, fully encoded as a TLV."""
+ return bchr(self.typeTag) + self._lengthOctets(len(self.payload)) + self.payload
+
+ def _decodeLen(self, idx, der):
+ """Given a (part of a) DER element, and an index to the first byte of
+ a DER length tag (L), return a tuple with the payload size,
+ and the index of the first byte of the such payload (V).
+
+ Raises a ValueError exception if the DER length is invalid.
+ Raises an IndexError exception if the DER element is too short.
+ """
+ length = bord(der[idx])
+ if length<=127:
+ return (length,idx+1)
+ payloadLength = bytes_to_long(der[idx+1:idx+1+(length & 0x7F)])
+ if payloadLength<=127:
+ raise ValueError("Not a DER length tag.")
+ return (payloadLength, idx+1+(length & 0x7F))
+
+ def decode(self, derEle, noLeftOvers=0):
+ """Decode a complete DER element, and re-initializes this
+ object with it.
+
+ @param derEle A complete DER element. It must start with a DER T
+ tag.
+ @param noLeftOvers Indicate whether it is acceptable to complete the
+ parsing of the DER element and find that not all
+ bytes in derEle have been used.
+ @return Index of the first unused byte in the given DER element.
+
+ Raises a ValueError exception in case of parsing errors.
+ Raises an IndexError exception if the DER element is too short.
+ """
+ try:
+ self.typeTag = bord(derEle[0])
+ if (self.typeTag & 0x1F)==0x1F:
+ raise ValueError("Unsupported DER tag")
+ (length,idx) = self._decodeLen(1, derEle)
+ if noLeftOvers and len(derEle) != (idx+length):
+ raise ValueError("Not a DER structure")
+ self.payload = derEle[idx:idx+length]
+ except IndexError:
+ raise ValueError("Not a valid DER SEQUENCE.")
+ return idx+length
+
+class DerInteger(DerObject):
+ def __init__(self, value = 0):
+ """Class to model an INTEGER DER element.
+
+ Limitation: only non-negative values are supported.
+ """
+ DerObject.__init__(self, 'INTEGER')
+ self.value = value
+
+ def encode(self):
+ """Return a complete INTEGER DER element, fully encoded as a TLV."""
+ self.payload = long_to_bytes(self.value)
+ if bord(self.payload[0])>127:
+ self.payload = bchr(0x00) + self.payload
+ return DerObject.encode(self)
+
+ def decode(self, derEle, noLeftOvers=0):
+ """Decode a complete INTEGER DER element, and re-initializes this
+ object with it.
+
+ @param derEle A complete INTEGER DER element. It must start with a DER
+ INTEGER tag.
+ @param noLeftOvers Indicate whether it is acceptable to complete the
+ parsing of the DER element and find that not all
+ bytes in derEle have been used.
+ @return Index of the first unused byte in the given DER element.
+
+ Raises a ValueError exception if the DER element is not a
+ valid non-negative INTEGER.
+ Raises an IndexError exception if the DER element is too short.
+ """
+ tlvLength = DerObject.decode(self, derEle, noLeftOvers)
+ if self.typeTag!=self.typeTags['INTEGER']:
+ raise ValueError ("Not a DER INTEGER.")
+ if bord(self.payload[0])>127:
+ raise ValueError ("Negative INTEGER.")
+ self.value = bytes_to_long(self.payload)
+ return tlvLength
+
+class DerSequence(DerObject):
+ """Class to model a SEQUENCE DER element.
+
+ This object behave like a dynamic Python sequence.
+ Sub-elements that are INTEGERs, look like Python integers.
+ Any other sub-element is a binary string encoded as the complete DER
+ sub-element (TLV).
+ """
+
+ def __init__(self, startSeq=None):
+ """Initialize the SEQUENCE DER object. Always empty
+ initially."""
+ DerObject.__init__(self, 'SEQUENCE')
+ if startSeq==None:
+ self._seq = []
+ else:
+ self._seq = startSeq
+
+ ## A few methods to make it behave like a python sequence
+
+ def __delitem__(self, n):
+ del self._seq[n]
+ def __getitem__(self, n):
+ return self._seq[n]
+ def __setitem__(self, key, value):
+ self._seq[key] = value
+ def __setslice__(self,i,j,sequence):
+ self._seq[i:j] = sequence
+ def __delslice__(self,i,j):
+ del self._seq[i:j]
+ def __getslice__(self, i, j):
+ return self._seq[max(0, i):max(0, j)]
+ def __len__(self):
+ return len(self._seq)
+ def append(self, item):
+ return self._seq.append(item)
+
+ def hasInts(self):
+ """Return the number of items in this sequence that are numbers."""
+ return len(filter(isInt, self._seq))
+
+ def hasOnlyInts(self):
+ """Return True if all items in this sequence are numbers."""
+ return self._seq and self.hasInts()==len(self._seq)
+
+ def encode(self):
+ """Return the DER encoding for the ASN.1 SEQUENCE, containing
+ the non-negative integers and longs added to this object.
+
+ Limitation: Raises a ValueError exception if it some elements
+ in the sequence are neither Python integers nor complete DER INTEGERs.
+ """
+ self.payload = b('')
+ for item in self._seq:
+ try:
+ self.payload += item
+ except:
+ try:
+ self.payload += DerInteger(item).encode()
+ except:
+ raise ValueError("Trying to DER encode an unknown object")
+ return DerObject.encode(self)
+
+ def decode(self, derEle, noLeftOvers=0):
+ """Decode a complete SEQUENCE DER element, and re-initializes this
+ object with it.
+
+ @param derEle A complete SEQUENCE DER element. It must start with a DER
+ SEQUENCE tag.
+ @param noLeftOvers Indicate whether it is acceptable to complete the
+ parsing of the DER element and find that not all
+ bytes in derEle have been used.
+ @return Index of the first unused byte in the given DER element.
+
+ DER INTEGERs are decoded into Python integers. Any other DER
+ element is not decoded. Its validity is not checked.
+
+ Raises a ValueError exception if the DER element is not a
+ valid DER SEQUENCE.
+ Raises an IndexError exception if the DER element is too short.
+ """
+
+ self._seq = []
+ try:
+ tlvLength = DerObject.decode(self, derEle, noLeftOvers)
+ if self.typeTag!=self.typeTags['SEQUENCE']:
+ raise ValueError("Not a DER SEQUENCE.")
+ # Scan one TLV at once
+ idx = 0
+ while idx<len(self.payload):
+ typeTag = bord(self.payload[idx])
+ if typeTag==self.typeTags['INTEGER']:
+ newInteger = DerInteger()
+ idx += newInteger.decode(self.payload[idx:])
+ self._seq.append(newInteger.value)
+ else:
+ itemLen,itemIdx = self._decodeLen(idx+1,self.payload)
+ self._seq.append(self.payload[idx:itemIdx+itemLen])
+ idx = itemIdx + itemLen
+ except IndexError:
+ raise ValueError("Not a valid DER SEQUENCE.")
+ return tlvLength
+
+class DerOctetString(DerObject):
+ def __init__(self, value = b('')):
+ DerObject.__init__(self, 'OCTET STRING')
+ self.payload = value
+
+ def decode(self, derEle, noLeftOvers=0):
+ p = DerObject.decode(derEle, noLeftOvers)
+ if not self.isType("OCTET STRING"):
+ raise ValueError("Not a valid OCTET STRING.")
+ return p
+
+class DerNull(DerObject):
+ def __init__(self):
+ DerObject.__init__(self, 'NULL')
+
+class DerObjectId(DerObject):
+ def __init__(self):
+ DerObject.__init__(self, 'OBJECT IDENTIFIER')
+
+ def decode(self, derEle, noLeftOvers=0):
+ p = DerObject.decode(derEle, noLeftOvers)
+ if not self.isType("OBJECT IDENTIFIER"):
+ raise ValueError("Not a valid OBJECT IDENTIFIER.")
+ return p
+
+def isInt(x):
+ test = 0
+ try:
+ test += x
+ except TypeError:
+ return 0
+ return 1
+
diff --git a/lib/Python/Lib/Crypto/Util/number.py b/lib/Python/Lib/Crypto/Util/number.py
new file mode 100644
index 000000000..2b5beb64f
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/number.py
@@ -0,0 +1,1456 @@
+#
+# number.py : Number-theoretic functions
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew M. Kuchling, Barry A. Warsaw, and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+#
+
+__revision__ = "$Id$"
+
+from Crypto.pct_warnings import GetRandomNumber_DeprecationWarning, PowmInsecureWarning
+from warnings import warn as _warn
+import math
+import sys
+from Crypto.Util.py3compat import *
+
+bignum = long
+try:
+ from Crypto.PublicKey import _fastmath
+except ImportError:
+ # For production, we are going to let import issues due to gmp/mpir shared
+ # libraries not loading slide silently and use slowmath. If you'd rather
+ # see an exception raised if _fastmath exists but cannot be imported,
+ # uncomment the below
+ #
+ # from distutils.sysconfig import get_config_var
+ # import inspect, os
+ # _fm_path = os.path.normpath(os.path.dirname(os.path.abspath(
+ # inspect.getfile(inspect.currentframe())))
+ # +"/../../PublicKey/_fastmath"+get_config_var("SO"))
+ # if os.path.exists(_fm_path):
+ # raise ImportError("While the _fastmath module exists, importing "+
+ # "it failed. This may point to the gmp or mpir shared library "+
+ # "not being in the path. _fastmath was found at "+_fm_path)
+ _fastmath = None
+
+# You need libgmp v5 or later to get mpz_powm_sec. Warn if it's not available.
+if _fastmath is not None and not _fastmath.HAVE_DECL_MPZ_POWM_SEC:
+ _warn("Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability.", PowmInsecureWarning)
+
+# New functions
+from _number_new import *
+
+# Commented out and replaced with faster versions below
+## def long2str(n):
+## s=''
+## while n>0:
+## s=chr(n & 255)+s
+## n=n>>8
+## return s
+
+## import types
+## def str2long(s):
+## if type(s)!=types.StringType: return s # Integers will be left alone
+## return reduce(lambda x,y : x*256+ord(y), s, 0L)
+
+def size (N):
+ """size(N:long) : int
+ Returns the size of the number N in bits.
+ """
+ bits = 0
+ while N >> bits:
+ bits += 1
+ return bits
+
+def getRandomNumber(N, randfunc=None):
+ """Deprecated. Use getRandomInteger or getRandomNBitInteger instead."""
+ warnings.warn("Crypto.Util.number.getRandomNumber has confusing semantics"+
+ "and has been deprecated. Use getRandomInteger or getRandomNBitInteger instead.",
+ GetRandomNumber_DeprecationWarning)
+ return getRandomNBitInteger(N, randfunc)
+
+def getRandomInteger(N, randfunc=None):
+ """getRandomInteger(N:int, randfunc:callable):long
+ Return a random number with at most N bits.
+
+ If randfunc is omitted, then Random.new().read is used.
+
+ This function is for internal use only and may be renamed or removed in
+ the future.
+ """
+ if randfunc is None:
+ _import_Random()
+ randfunc = Random.new().read
+
+ S = randfunc(N>>3)
+ odd_bits = N % 8
+ if odd_bits != 0:
+ char = ord(randfunc(1)) >> (8-odd_bits)
+ S = bchr(char) + S
+ value = bytes_to_long(S)
+ return value
+
+def getRandomRange(a, b, randfunc=None):
+ """getRandomRange(a:int, b:int, randfunc:callable):long
+ Return a random number n so that a <= n < b.
+
+ If randfunc is omitted, then Random.new().read is used.
+
+ This function is for internal use only and may be renamed or removed in
+ the future.
+ """
+ range_ = b - a - 1
+ bits = size(range_)
+ value = getRandomInteger(bits, randfunc)
+ while value > range_:
+ value = getRandomInteger(bits, randfunc)
+ return a + value
+
+def getRandomNBitInteger(N, randfunc=None):
+ """getRandomInteger(N:int, randfunc:callable):long
+ Return a random number with exactly N-bits, i.e. a random number
+ between 2**(N-1) and (2**N)-1.
+
+ If randfunc is omitted, then Random.new().read is used.
+
+ This function is for internal use only and may be renamed or removed in
+ the future.
+ """
+ value = getRandomInteger (N-1, randfunc)
+ value |= 2L ** (N-1) # Ensure high bit is set
+ assert size(value) >= N
+ return value
+
+def GCD(x,y):
+ """GCD(x:long, y:long): long
+ Return the GCD of x and y.
+ """
+ x = abs(x) ; y = abs(y)
+ while x > 0:
+ x, y = y % x, x
+ return y
+
+def inverse(u, v):
+ """inverse(u:long, v:long):long
+ Return the inverse of u mod v.
+ """
+ u3, v3 = long(u), long(v)
+ u1, v1 = 1L, 0L
+ while v3 > 0:
+ q=divmod(u3, v3)[0]
+ u1, v1 = v1, u1 - v1*q
+ u3, v3 = v3, u3 - v3*q
+ while u1<0:
+ u1 = u1 + v
+ return u1
+
+# Given a number of bits to generate and a random generation function,
+# find a prime number of the appropriate size.
+
+def getPrime(N, randfunc=None):
+ """getPrime(N:int, randfunc:callable):long
+ Return a random N-bit prime number.
+
+ If randfunc is omitted, then Random.new().read is used.
+ """
+ if randfunc is None:
+ _import_Random()
+ randfunc = Random.new().read
+
+ number=getRandomNBitInteger(N, randfunc) | 1
+ while (not isPrime(number, randfunc=randfunc)):
+ number=number+2
+ return number
+
+
+def _rabinMillerTest(n, rounds, randfunc=None):
+ """_rabinMillerTest(n:long, rounds:int, randfunc:callable):int
+ Tests if n is prime.
+ Returns 0 when n is definitly composite.
+ Returns 1 when n is probably prime.
+ Returns 2 when n is definitly prime.
+
+ If randfunc is omitted, then Random.new().read is used.
+
+ This function is for internal use only and may be renamed or removed in
+ the future.
+ """
+ # check special cases (n==2, n even, n < 2)
+ if n < 3 or (n & 1) == 0:
+ return n == 2
+ # n might be very large so it might be beneficial to precalculate n-1
+ n_1 = n - 1
+ # determine m and b so that 2**b * m = n - 1 and b maximal
+ b = 0
+ m = n_1
+ while (m & 1) == 0:
+ b += 1
+ m >>= 1
+
+ tested = []
+ # we need to do at most n-2 rounds.
+ for i in xrange (min (rounds, n-2)):
+ # randomly choose a < n and make sure it hasn't been tested yet
+ a = getRandomRange (2, n, randfunc)
+ while a in tested:
+ a = getRandomRange (2, n, randfunc)
+ tested.append (a)
+ # do the rabin-miller test
+ z = pow (a, m, n) # (a**m) % n
+ if z == 1 or z == n_1:
+ continue
+ composite = 1
+ for r in xrange (b):
+ z = (z * z) % n
+ if z == 1:
+ return 0
+ elif z == n_1:
+ composite = 0
+ break
+ if composite:
+ return 0
+ return 1
+
+def getStrongPrime(N, e=0, false_positive_prob=1e-6, randfunc=None):
+ """getStrongPrime(N:int, e:int, false_positive_prob:float, randfunc:callable):long
+ Return a random strong N-bit prime number.
+ In this context p is a strong prime if p-1 and p+1 have at
+ least one large prime factor.
+ N should be a multiple of 128 and > 512.
+
+ If e is provided the returned prime p-1 will be coprime to e
+ and thus suitable for RSA where e is the public exponent.
+
+ The optional false_positive_prob is the statistical probability
+ that true is returned even though it is not (pseudo-prime).
+ It defaults to 1e-6 (less than 1:1000000).
+ Note that the real probability of a false-positive is far less. This is
+ just the mathematically provable limit.
+
+ randfunc should take a single int parameter and return that
+ many random bytes as a string.
+ If randfunc is omitted, then Random.new().read is used.
+ """
+ # This function was implemented following the
+ # instructions found in the paper:
+ # "FAST GENERATION OF RANDOM, STRONG RSA PRIMES"
+ # by Robert D. Silverman
+ # RSA Laboratories
+ # May 17, 1997
+ # which by the time of writing could be freely downloaded here:
+ # http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.17.2713&rep=rep1&type=pdf
+
+ # Use the accelerator if available
+ if _fastmath is not None:
+ return _fastmath.getStrongPrime(long(N), long(e), false_positive_prob,
+ randfunc)
+
+ if (N < 512) or ((N % 128) != 0):
+ raise ValueError ("bits must be multiple of 128 and > 512")
+
+ rabin_miller_rounds = int(math.ceil(-math.log(false_positive_prob)/math.log(4)))
+
+ # calculate range for X
+ # lower_bound = sqrt(2) * 2^{511 + 128*x}
+ # upper_bound = 2^{512 + 128*x} - 1
+ x = (N - 512) >> 7;
+ # We need to approximate the sqrt(2) in the lower_bound by an integer
+ # expression because floating point math overflows with these numbers
+ lower_bound = divmod(14142135623730950489L * (2L ** (511 + 128*x)),
+ 10000000000000000000L)[0]
+ upper_bound = (1L << (512 + 128*x)) - 1
+ # Randomly choose X in calculated range
+ X = getRandomRange (lower_bound, upper_bound, randfunc)
+
+ # generate p1 and p2
+ p = [0, 0]
+ for i in (0, 1):
+ # randomly choose 101-bit y
+ y = getRandomNBitInteger (101, randfunc)
+ # initialize the field for sieving
+ field = [0] * 5 * len (sieve_base)
+ # sieve the field
+ for prime in sieve_base:
+ offset = y % prime
+ for j in xrange ((prime - offset) % prime, len (field), prime):
+ field[j] = 1
+
+ # look for suitable p[i] starting at y
+ result = 0
+ for j in range(len(field)):
+ composite = field[j]
+ # look for next canidate
+ if composite:
+ continue
+ tmp = y + j
+ result = _rabinMillerTest (tmp, rabin_miller_rounds)
+ if result > 0:
+ p[i] = tmp
+ break
+ if result == 0:
+ raise RuntimeError ("Couln't find prime in field. "
+ "Developer: Increase field_size")
+
+ # Calculate R
+ # R = (p2^{-1} mod p1) * p2 - (p1^{-1} mod p2) * p1
+ tmp1 = inverse (p[1], p[0]) * p[1] # (p2^-1 mod p1)*p2
+ tmp2 = inverse (p[0], p[1]) * p[0] # (p1^-1 mod p2)*p1
+ R = tmp1 - tmp2 # (p2^-1 mod p1)*p2 - (p1^-1 mod p2)*p1
+
+ # search for final prime number starting by Y0
+ # Y0 = X + (R - X mod p1p2)
+ increment = p[0] * p[1]
+ X = X + (R - (X % increment))
+ while 1:
+ is_possible_prime = 1
+ # first check candidate against sieve_base
+ for prime in sieve_base:
+ if (X % prime) == 0:
+ is_possible_prime = 0
+ break
+ # if e is given make sure that e and X-1 are coprime
+ # this is not necessarily a strong prime criterion but useful when
+ # creating them for RSA where the p-1 and q-1 should be coprime to
+ # the public exponent e
+ if e and is_possible_prime:
+ if e & 1:
+ if GCD (e, X-1) != 1:
+ is_possible_prime = 0
+ else:
+ if GCD (e, divmod((X-1),2)[0]) != 1:
+ is_possible_prime = 0
+
+ # do some Rabin-Miller-Tests
+ if is_possible_prime:
+ result = _rabinMillerTest (X, rabin_miller_rounds)
+ if result > 0:
+ break
+ X += increment
+ # abort when X has more bits than requested
+ # TODO: maybe we shouldn't abort but rather start over.
+ if X >= 1L << N:
+ raise RuntimeError ("Couln't find prime in field. "
+ "Developer: Increase field_size")
+ return X
+
+def isPrime(N, false_positive_prob=1e-6, randfunc=None):
+ """isPrime(N:long, false_positive_prob:float, randfunc:callable):bool
+ Return true if N is prime.
+
+ The optional false_positive_prob is the statistical probability
+ that true is returned even though it is not (pseudo-prime).
+ It defaults to 1e-6 (less than 1:1000000).
+ Note that the real probability of a false-positive is far less. This is
+ just the mathematically provable limit.
+
+ If randfunc is omitted, then Random.new().read is used.
+ """
+ if _fastmath is not None:
+ return _fastmath.isPrime(long(N), false_positive_prob, randfunc)
+
+ if N < 3 or N & 1 == 0:
+ return N == 2
+ for p in sieve_base:
+ if N == p:
+ return 1
+ if N % p == 0:
+ return 0
+
+ rounds = int(math.ceil(-math.log(false_positive_prob)/math.log(4)))
+ return _rabinMillerTest(N, rounds, randfunc)
+
+
+# Improved conversion functions contributed by Barry Warsaw, after
+# careful benchmarking
+
+import struct
+
+def long_to_bytes(n, blocksize=0):
+ """long_to_bytes(n:long, blocksize:int) : string
+ Convert a long integer to a byte string.
+
+ If optional blocksize is given and greater than zero, pad the front of the
+ byte string with binary zeros so that the length is a multiple of
+ blocksize.
+ """
+ # after much testing, this algorithm was deemed to be the fastest
+ s = b('')
+ n = long(n)
+ pack = struct.pack
+ while n > 0:
+ s = pack('>I', n & 0xffffffffL) + s
+ n = n >> 32
+ # strip off leading zeros
+ for i in range(len(s)):
+ if s[i] != b('\000')[0]:
+ break
+ else:
+ # only happens when n == 0
+ s = b('\000')
+ i = 0
+ s = s[i:]
+ # add back some pad bytes. this could be done more efficiently w.r.t. the
+ # de-padding being done above, but sigh...
+ if blocksize > 0 and len(s) % blocksize:
+ s = (blocksize - len(s) % blocksize) * b('\000') + s
+ return s
+
+def bytes_to_long(s):
+ """bytes_to_long(string) : long
+ Convert a byte string to a long integer.
+
+ This is (essentially) the inverse of long_to_bytes().
+ """
+ acc = 0L
+ unpack = struct.unpack
+ length = len(s)
+ if length % 4:
+ extra = (4 - length % 4)
+ s = b('\000') * extra + s
+ length = length + extra
+ for i in range(0, length, 4):
+ acc = (acc << 32) + unpack('>I', s[i:i+4])[0]
+ return acc
+
+# For backwards compatibility...
+import warnings
+def long2str(n, blocksize=0):
+ warnings.warn("long2str() has been replaced by long_to_bytes()")
+ return long_to_bytes(n, blocksize)
+def str2long(s):
+ warnings.warn("str2long() has been replaced by bytes_to_long()")
+ return bytes_to_long(s)
+
+def _import_Random():
+ # This is called in a function instead of at the module level in order to
+ # avoid problems with recursive imports
+ global Random, StrongRandom
+ from Crypto import Random
+ from Crypto.Random.random import StrongRandom
+
+
+
+# The first 10000 primes used for checking primality.
+# This should be enough to eliminate most of the odd
+# numbers before needing to do a Rabin-Miller test at all.
+sieve_base = (
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
+ 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
+ 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
+ 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
+ 179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
+ 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
+ 283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
+ 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
+ 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
+ 467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
+ 547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
+ 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
+ 661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
+ 739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
+ 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
+ 877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
+ 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
+ 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
+ 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
+ 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
+ 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
+ 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
+ 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
+ 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
+ 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
+ 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657,
+ 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733,
+ 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811,
+ 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889,
+ 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
+ 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
+ 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129,
+ 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213,
+ 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287,
+ 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357,
+ 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
+ 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531,
+ 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617,
+ 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687,
+ 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
+ 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819,
+ 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
+ 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999,
+ 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079,
+ 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181,
+ 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257,
+ 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331,
+ 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
+ 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511,
+ 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571,
+ 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643,
+ 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727,
+ 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821,
+ 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907,
+ 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989,
+ 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057,
+ 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139,
+ 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231,
+ 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297,
+ 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409,
+ 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493,
+ 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583,
+ 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657,
+ 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751,
+ 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831,
+ 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937,
+ 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003,
+ 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087,
+ 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179,
+ 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279,
+ 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387,
+ 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443,
+ 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521,
+ 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639,
+ 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693,
+ 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791,
+ 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857,
+ 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939,
+ 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053,
+ 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133,
+ 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221,
+ 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301,
+ 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367,
+ 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473,
+ 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571,
+ 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673,
+ 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761,
+ 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833,
+ 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917,
+ 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997,
+ 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103,
+ 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207,
+ 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297,
+ 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411,
+ 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499,
+ 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561,
+ 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643,
+ 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723,
+ 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829,
+ 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919,
+ 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017,
+ 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111,
+ 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219,
+ 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291,
+ 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387,
+ 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501,
+ 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597,
+ 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677,
+ 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741,
+ 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831,
+ 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929,
+ 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011,
+ 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109,
+ 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199,
+ 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283,
+ 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377,
+ 9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439,
+ 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533,
+ 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631,
+ 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733,
+ 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811,
+ 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887,
+ 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007,
+ 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099,
+ 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177,
+ 10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271,
+ 10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343,
+ 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459,
+ 10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567,
+ 10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657,
+ 10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739,
+ 10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859,
+ 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949,
+ 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059,
+ 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149,
+ 11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251,
+ 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329,
+ 11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443,
+ 11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527,
+ 11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657,
+ 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777,
+ 11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833,
+ 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933,
+ 11939, 11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011,
+ 12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 12109,
+ 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211,
+ 12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289,
+ 12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401,
+ 12409, 12413, 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487,
+ 12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553,
+ 12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, 12641,
+ 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739,
+ 12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829,
+ 12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923,
+ 12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007,
+ 13009, 13033, 13037, 13043, 13049, 13063, 13093, 13099, 13103, 13109,
+ 13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187,
+ 13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309,
+ 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411,
+ 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499,
+ 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619,
+ 13627, 13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697,
+ 13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 13781,
+ 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879,
+ 13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967,
+ 13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081,
+ 14083, 14087, 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197,
+ 14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323,
+ 14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, 14419,
+ 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519,
+ 14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593,
+ 14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699,
+ 14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767,
+ 14771, 14779, 14783, 14797, 14813, 14821, 14827, 14831, 14843, 14851,
+ 14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947,
+ 14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073,
+ 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149,
+ 15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259,
+ 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319,
+ 15329, 15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401,
+ 15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 15497,
+ 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607,
+ 15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679,
+ 15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773,
+ 15787, 15791, 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881,
+ 15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971,
+ 15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, 16069,
+ 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183,
+ 16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267,
+ 16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381,
+ 16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481,
+ 16487, 16493, 16519, 16529, 16547, 16553, 16561, 16567, 16573, 16603,
+ 16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691,
+ 16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811,
+ 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903,
+ 16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993,
+ 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093,
+ 17099, 17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191,
+ 17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 17317,
+ 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389,
+ 17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477,
+ 17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573,
+ 17579, 17581, 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669,
+ 17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783,
+ 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, 17891,
+ 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, 17959, 17971,
+ 17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059,
+ 18061, 18077, 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143,
+ 18149, 18169, 18181, 18191, 18199, 18211, 18217, 18223, 18229, 18233,
+ 18251, 18253, 18257, 18269, 18287, 18289, 18301, 18307, 18311, 18313,
+ 18329, 18341, 18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427,
+ 18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517,
+ 18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637,
+ 18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749,
+ 18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, 18869, 18899,
+ 18911, 18913, 18917, 18919, 18947, 18959, 18973, 18979, 19001, 19009,
+ 19013, 19031, 19037, 19051, 19069, 19073, 19079, 19081, 19087, 19121,
+ 19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, 19213, 19219,
+ 19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319,
+ 19333, 19373, 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423,
+ 19427, 19429, 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477,
+ 19483, 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571,
+ 19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, 19699,
+ 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, 19777, 19793,
+ 19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891,
+ 19913, 19919, 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991,
+ 19993, 19997, 20011, 20021, 20023, 20029, 20047, 20051, 20063, 20071,
+ 20089, 20101, 20107, 20113, 20117, 20123, 20129, 20143, 20147, 20149,
+ 20161, 20173, 20177, 20183, 20201, 20219, 20231, 20233, 20249, 20261,
+ 20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357,
+ 20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443,
+ 20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551,
+ 20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, 20681, 20693,
+ 20707, 20717, 20719, 20731, 20743, 20747, 20749, 20753, 20759, 20771,
+ 20773, 20789, 20807, 20809, 20849, 20857, 20873, 20879, 20887, 20897,
+ 20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, 20981, 20983,
+ 21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067,
+ 21089, 21101, 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169,
+ 21179, 21187, 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277,
+ 21283, 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383,
+ 21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, 21491,
+ 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, 21559, 21563,
+ 21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647,
+ 21649, 21661, 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751,
+ 21757, 21767, 21773, 21787, 21799, 21803, 21817, 21821, 21839, 21841,
+ 21851, 21859, 21863, 21871, 21881, 21893, 21911, 21929, 21937, 21943,
+ 21961, 21977, 21991, 21997, 22003, 22013, 22027, 22031, 22037, 22039,
+ 22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123,
+ 22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229,
+ 22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307,
+ 22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, 22433, 22441,
+ 22447, 22453, 22469, 22481, 22483, 22501, 22511, 22531, 22541, 22543,
+ 22549, 22567, 22571, 22573, 22613, 22619, 22621, 22637, 22639, 22643,
+ 22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, 22721, 22727,
+ 22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817,
+ 22853, 22859, 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943,
+ 22961, 22963, 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029,
+ 23039, 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099,
+ 23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, 23203,
+ 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, 23311, 23321,
+ 23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447,
+ 23459, 23473, 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561,
+ 23563, 23567, 23581, 23593, 23599, 23603, 23609, 23623, 23627, 23629,
+ 23633, 23663, 23669, 23671, 23677, 23687, 23689, 23719, 23741, 23743,
+ 23747, 23753, 23761, 23767, 23773, 23789, 23801, 23813, 23819, 23827,
+ 23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909,
+ 23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007,
+ 24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091,
+ 24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, 24151, 24169,
+ 24179, 24181, 24197, 24203, 24223, 24229, 24239, 24247, 24251, 24281,
+ 24317, 24329, 24337, 24359, 24371, 24373, 24379, 24391, 24407, 24413,
+ 24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, 24509, 24517,
+ 24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659,
+ 24671, 24677, 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767,
+ 24781, 24793, 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877,
+ 24889, 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977,
+ 24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, 25097,
+ 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, 25171, 25183,
+ 25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303,
+ 25307, 25309, 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391,
+ 25409, 25411, 25423, 25439, 25447, 25453, 25457, 25463, 25469, 25471,
+ 25523, 25537, 25541, 25561, 25577, 25579, 25583, 25589, 25601, 25603,
+ 25609, 25621, 25633, 25639, 25643, 25657, 25667, 25673, 25679, 25693,
+ 25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799,
+ 25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913,
+ 25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999,
+ 26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, 26107, 26111,
+ 26113, 26119, 26141, 26153, 26161, 26171, 26177, 26183, 26189, 26203,
+ 26209, 26227, 26237, 26249, 26251, 26261, 26263, 26267, 26293, 26297,
+ 26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, 26393, 26399,
+ 26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497,
+ 26501, 26513, 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633,
+ 26641, 26647, 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711,
+ 26713, 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801,
+ 26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, 26891,
+ 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, 26981, 26987,
+ 26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077,
+ 27091, 27103, 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211,
+ 27239, 27241, 27253, 27259, 27271, 27277, 27281, 27283, 27299, 27329,
+ 27337, 27361, 27367, 27397, 27407, 27409, 27427, 27431, 27437, 27449,
+ 27457, 27479, 27481, 27487, 27509, 27527, 27529, 27539, 27541, 27551,
+ 27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691,
+ 27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767,
+ 27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827,
+ 27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, 27943, 27947,
+ 27953, 27961, 27967, 27983, 27997, 28001, 28019, 28027, 28031, 28051,
+ 28057, 28069, 28081, 28087, 28097, 28099, 28109, 28111, 28123, 28151,
+ 28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, 28279, 28283,
+ 28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403,
+ 28409, 28411, 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499,
+ 28513, 28517, 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579,
+ 28591, 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649,
+ 28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, 28729,
+ 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, 28817, 28837,
+ 28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933,
+ 28949, 28961, 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059,
+ 29063, 29077, 29101, 29123, 29129, 29131, 29137, 29147, 29153, 29167,
+ 29173, 29179, 29191, 29201, 29207, 29209, 29221, 29231, 29243, 29251,
+ 29269, 29287, 29297, 29303, 29311, 29327, 29333, 29339, 29347, 29363,
+ 29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443,
+ 29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573,
+ 29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671,
+ 29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, 29803, 29819,
+ 29833, 29837, 29851, 29863, 29867, 29873, 29879, 29881, 29917, 29921,
+ 29927, 29947, 29959, 29983, 29989, 30011, 30013, 30029, 30047, 30059,
+ 30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, 30133, 30137,
+ 30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241,
+ 30253, 30259, 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341,
+ 30347, 30367, 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469,
+ 30491, 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559,
+ 30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, 30689,
+ 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, 30781, 30803,
+ 30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871,
+ 30881, 30893, 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983,
+ 31013, 31019, 31033, 31039, 31051, 31063, 31069, 31079, 31081, 31091,
+ 31121, 31123, 31139, 31147, 31151, 31153, 31159, 31177, 31181, 31183,
+ 31189, 31193, 31219, 31223, 31231, 31237, 31247, 31249, 31253, 31259,
+ 31267, 31271, 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357,
+ 31379, 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511,
+ 31513, 31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601,
+ 31607, 31627, 31643, 31649, 31657, 31663, 31667, 31687, 31699, 31721,
+ 31723, 31727, 31729, 31741, 31751, 31769, 31771, 31793, 31799, 31817,
+ 31847, 31849, 31859, 31873, 31883, 31891, 31907, 31957, 31963, 31973,
+ 31981, 31991, 32003, 32009, 32027, 32029, 32051, 32057, 32059, 32063,
+ 32069, 32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159,
+ 32173, 32183, 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257,
+ 32261, 32297, 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353,
+ 32359, 32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423,
+ 32429, 32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, 32531,
+ 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, 32603, 32609,
+ 32611, 32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717,
+ 32719, 32749, 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831,
+ 32833, 32839, 32843, 32869, 32887, 32909, 32911, 32917, 32933, 32939,
+ 32941, 32957, 32969, 32971, 32983, 32987, 32993, 32999, 33013, 33023,
+ 33029, 33037, 33049, 33053, 33071, 33073, 33083, 33091, 33107, 33113,
+ 33119, 33149, 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211,
+ 33223, 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343,
+ 33347, 33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427,
+ 33457, 33461, 33469, 33479, 33487, 33493, 33503, 33521, 33529, 33533,
+ 33547, 33563, 33569, 33577, 33581, 33587, 33589, 33599, 33601, 33613,
+ 33617, 33619, 33623, 33629, 33637, 33641, 33647, 33679, 33703, 33713,
+ 33721, 33739, 33749, 33751, 33757, 33767, 33769, 33773, 33791, 33797,
+ 33809, 33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893,
+ 33911, 33923, 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031,
+ 34033, 34039, 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157,
+ 34159, 34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261,
+ 34267, 34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, 34337,
+ 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, 34439, 34457,
+ 34469, 34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537,
+ 34543, 34549, 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649,
+ 34651, 34667, 34673, 34679, 34687, 34693, 34703, 34721, 34729, 34739,
+ 34747, 34757, 34759, 34763, 34781, 34807, 34819, 34841, 34843, 34847,
+ 34849, 34871, 34877, 34883, 34897, 34913, 34919, 34939, 34949, 34961,
+ 34963, 34981, 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083,
+ 35089, 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159,
+ 35171, 35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291,
+ 35311, 35317, 35323, 35327, 35339, 35353, 35363, 35381, 35393, 35401,
+ 35407, 35419, 35423, 35437, 35447, 35449, 35461, 35491, 35507, 35509,
+ 35521, 35527, 35531, 35533, 35537, 35543, 35569, 35573, 35591, 35593,
+ 35597, 35603, 35617, 35671, 35677, 35729, 35731, 35747, 35753, 35759,
+ 35771, 35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863,
+ 35869, 35879, 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969,
+ 35977, 35983, 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061,
+ 36067, 36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161,
+ 36187, 36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, 36277,
+ 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, 36373, 36383,
+ 36389, 36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497,
+ 36523, 36527, 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587,
+ 36599, 36607, 36629, 36637, 36643, 36653, 36671, 36677, 36683, 36691,
+ 36697, 36709, 36713, 36721, 36739, 36749, 36761, 36767, 36779, 36781,
+ 36787, 36791, 36793, 36809, 36821, 36833, 36847, 36857, 36871, 36877,
+ 36887, 36899, 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947,
+ 36973, 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057,
+ 37061, 37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189,
+ 37199, 37201, 37217, 37223, 37243, 37253, 37273, 37277, 37307, 37309,
+ 37313, 37321, 37337, 37339, 37357, 37361, 37363, 37369, 37379, 37397,
+ 37409, 37423, 37441, 37447, 37463, 37483, 37489, 37493, 37501, 37507,
+ 37511, 37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573,
+ 37579, 37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663,
+ 37691, 37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813,
+ 37831, 37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951,
+ 37957, 37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039, 38047,
+ 38053, 38069, 38083, 38113, 38119, 38149, 38153, 38167, 38177, 38183,
+ 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, 38273, 38281,
+ 38287, 38299, 38303, 38317, 38321, 38327, 38329, 38333, 38351, 38371,
+ 38377, 38393, 38431, 38447, 38449, 38453, 38459, 38461, 38501, 38543,
+ 38557, 38561, 38567, 38569, 38593, 38603, 38609, 38611, 38629, 38639,
+ 38651, 38653, 38669, 38671, 38677, 38693, 38699, 38707, 38711, 38713,
+ 38723, 38729, 38737, 38747, 38749, 38767, 38783, 38791, 38803, 38821,
+ 38833, 38839, 38851, 38861, 38867, 38873, 38891, 38903, 38917, 38921,
+ 38923, 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041,
+ 39043, 39047, 39079, 39089, 39097, 39103, 39107, 39113, 39119, 39133,
+ 39139, 39157, 39161, 39163, 39181, 39191, 39199, 39209, 39217, 39227,
+ 39229, 39233, 39239, 39241, 39251, 39293, 39301, 39313, 39317, 39323,
+ 39341, 39343, 39359, 39367, 39371, 39373, 39383, 39397, 39409, 39419,
+ 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, 39521, 39541,
+ 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667,
+ 39671, 39679, 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769,
+ 39779, 39791, 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857,
+ 39863, 39869, 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971,
+ 39979, 39983, 39989, 40009, 40013, 40031, 40037, 40039, 40063, 40087,
+ 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, 40163, 40169,
+ 40177, 40189, 40193, 40213, 40231, 40237, 40241, 40253, 40277, 40283,
+ 40289, 40343, 40351, 40357, 40361, 40387, 40423, 40427, 40429, 40433,
+ 40459, 40471, 40483, 40487, 40493, 40499, 40507, 40519, 40529, 40531,
+ 40543, 40559, 40577, 40583, 40591, 40597, 40609, 40627, 40637, 40639,
+ 40693, 40697, 40699, 40709, 40739, 40751, 40759, 40763, 40771, 40787,
+ 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867,
+ 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973,
+ 40993, 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081,
+ 41113, 41117, 41131, 41141, 41143, 41149, 41161, 41177, 41179, 41183,
+ 41189, 41201, 41203, 41213, 41221, 41227, 41231, 41233, 41243, 41257,
+ 41263, 41269, 41281, 41299, 41333, 41341, 41351, 41357, 41381, 41387,
+ 41389, 41399, 41411, 41413, 41443, 41453, 41467, 41479, 41491, 41507,
+ 41513, 41519, 41521, 41539, 41543, 41549, 41579, 41593, 41597, 41603,
+ 41609, 41611, 41617, 41621, 41627, 41641, 41647, 41651, 41659, 41669,
+ 41681, 41687, 41719, 41729, 41737, 41759, 41761, 41771, 41777, 41801,
+ 41809, 41813, 41843, 41849, 41851, 41863, 41879, 41887, 41893, 41897,
+ 41903, 41911, 41927, 41941, 41947, 41953, 41957, 41959, 41969, 41981,
+ 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, 42071, 42073,
+ 42083, 42089, 42101, 42131, 42139, 42157, 42169, 42179, 42181, 42187,
+ 42193, 42197, 42209, 42221, 42223, 42227, 42239, 42257, 42281, 42283,
+ 42293, 42299, 42307, 42323, 42331, 42337, 42349, 42359, 42373, 42379,
+ 42391, 42397, 42403, 42407, 42409, 42433, 42437, 42443, 42451, 42457,
+ 42461, 42463, 42467, 42473, 42487, 42491, 42499, 42509, 42533, 42557,
+ 42569, 42571, 42577, 42589, 42611, 42641, 42643, 42649, 42667, 42677,
+ 42683, 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743,
+ 42751, 42767, 42773, 42787, 42793, 42797, 42821, 42829, 42839, 42841,
+ 42853, 42859, 42863, 42899, 42901, 42923, 42929, 42937, 42943, 42953,
+ 42961, 42967, 42979, 42989, 43003, 43013, 43019, 43037, 43049, 43051,
+ 43063, 43067, 43093, 43103, 43117, 43133, 43151, 43159, 43177, 43189,
+ 43201, 43207, 43223, 43237, 43261, 43271, 43283, 43291, 43313, 43319,
+ 43321, 43331, 43391, 43397, 43399, 43403, 43411, 43427, 43441, 43451,
+ 43457, 43481, 43487, 43499, 43517, 43541, 43543, 43573, 43577, 43579,
+ 43591, 43597, 43607, 43609, 43613, 43627, 43633, 43649, 43651, 43661,
+ 43669, 43691, 43711, 43717, 43721, 43753, 43759, 43777, 43781, 43783,
+ 43787, 43789, 43793, 43801, 43853, 43867, 43889, 43891, 43913, 43933,
+ 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, 43997, 44017,
+ 44021, 44027, 44029, 44041, 44053, 44059, 44071, 44087, 44089, 44101,
+ 44111, 44119, 44123, 44129, 44131, 44159, 44171, 44179, 44189, 44201,
+ 44203, 44207, 44221, 44249, 44257, 44263, 44267, 44269, 44273, 44279,
+ 44281, 44293, 44351, 44357, 44371, 44381, 44383, 44389, 44417, 44449,
+ 44453, 44483, 44491, 44497, 44501, 44507, 44519, 44531, 44533, 44537,
+ 44543, 44549, 44563, 44579, 44587, 44617, 44621, 44623, 44633, 44641,
+ 44647, 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741,
+ 44753, 44771, 44773, 44777, 44789, 44797, 44809, 44819, 44839, 44843,
+ 44851, 44867, 44879, 44887, 44893, 44909, 44917, 44927, 44939, 44953,
+ 44959, 44963, 44971, 44983, 44987, 45007, 45013, 45053, 45061, 45077,
+ 45083, 45119, 45121, 45127, 45131, 45137, 45139, 45161, 45179, 45181,
+ 45191, 45197, 45233, 45247, 45259, 45263, 45281, 45289, 45293, 45307,
+ 45317, 45319, 45329, 45337, 45341, 45343, 45361, 45377, 45389, 45403,
+ 45413, 45427, 45433, 45439, 45481, 45491, 45497, 45503, 45523, 45533,
+ 45541, 45553, 45557, 45569, 45587, 45589, 45599, 45613, 45631, 45641,
+ 45659, 45667, 45673, 45677, 45691, 45697, 45707, 45737, 45751, 45757,
+ 45763, 45767, 45779, 45817, 45821, 45823, 45827, 45833, 45841, 45853,
+ 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, 45971, 45979,
+ 45989, 46021, 46027, 46049, 46051, 46061, 46073, 46091, 46093, 46099,
+ 46103, 46133, 46141, 46147, 46153, 46171, 46181, 46183, 46187, 46199,
+ 46219, 46229, 46237, 46261, 46271, 46273, 46279, 46301, 46307, 46309,
+ 46327, 46337, 46349, 46351, 46381, 46399, 46411, 46439, 46441, 46447,
+ 46451, 46457, 46471, 46477, 46489, 46499, 46507, 46511, 46523, 46549,
+ 46559, 46567, 46573, 46589, 46591, 46601, 46619, 46633, 46639, 46643,
+ 46649, 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747,
+ 46751, 46757, 46769, 46771, 46807, 46811, 46817, 46819, 46829, 46831,
+ 46853, 46861, 46867, 46877, 46889, 46901, 46919, 46933, 46957, 46993,
+ 46997, 47017, 47041, 47051, 47057, 47059, 47087, 47093, 47111, 47119,
+ 47123, 47129, 47137, 47143, 47147, 47149, 47161, 47189, 47207, 47221,
+ 47237, 47251, 47269, 47279, 47287, 47293, 47297, 47303, 47309, 47317,
+ 47339, 47351, 47353, 47363, 47381, 47387, 47389, 47407, 47417, 47419,
+ 47431, 47441, 47459, 47491, 47497, 47501, 47507, 47513, 47521, 47527,
+ 47533, 47543, 47563, 47569, 47581, 47591, 47599, 47609, 47623, 47629,
+ 47639, 47653, 47657, 47659, 47681, 47699, 47701, 47711, 47713, 47717,
+ 47737, 47741, 47743, 47777, 47779, 47791, 47797, 47807, 47809, 47819,
+ 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, 47933, 47939,
+ 47947, 47951, 47963, 47969, 47977, 47981, 48017, 48023, 48029, 48049,
+ 48073, 48079, 48091, 48109, 48119, 48121, 48131, 48157, 48163, 48179,
+ 48187, 48193, 48197, 48221, 48239, 48247, 48259, 48271, 48281, 48299,
+ 48311, 48313, 48337, 48341, 48353, 48371, 48383, 48397, 48407, 48409,
+ 48413, 48437, 48449, 48463, 48473, 48479, 48481, 48487, 48491, 48497,
+ 48523, 48527, 48533, 48539, 48541, 48563, 48571, 48589, 48593, 48611,
+ 48619, 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733,
+ 48751, 48757, 48761, 48767, 48779, 48781, 48787, 48799, 48809, 48817,
+ 48821, 48823, 48847, 48857, 48859, 48869, 48871, 48883, 48889, 48907,
+ 48947, 48953, 48973, 48989, 48991, 49003, 49009, 49019, 49031, 49033,
+ 49037, 49043, 49057, 49069, 49081, 49103, 49109, 49117, 49121, 49123,
+ 49139, 49157, 49169, 49171, 49177, 49193, 49199, 49201, 49207, 49211,
+ 49223, 49253, 49261, 49277, 49279, 49297, 49307, 49331, 49333, 49339,
+ 49363, 49367, 49369, 49391, 49393, 49409, 49411, 49417, 49429, 49433,
+ 49451, 49459, 49463, 49477, 49481, 49499, 49523, 49529, 49531, 49537,
+ 49547, 49549, 49559, 49597, 49603, 49613, 49627, 49633, 49639, 49663,
+ 49667, 49669, 49681, 49697, 49711, 49727, 49739, 49741, 49747, 49757,
+ 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, 49843, 49853,
+ 49871, 49877, 49891, 49919, 49921, 49927, 49937, 49939, 49943, 49957,
+ 49991, 49993, 49999, 50021, 50023, 50033, 50047, 50051, 50053, 50069,
+ 50077, 50087, 50093, 50101, 50111, 50119, 50123, 50129, 50131, 50147,
+ 50153, 50159, 50177, 50207, 50221, 50227, 50231, 50261, 50263, 50273,
+ 50287, 50291, 50311, 50321, 50329, 50333, 50341, 50359, 50363, 50377,
+ 50383, 50387, 50411, 50417, 50423, 50441, 50459, 50461, 50497, 50503,
+ 50513, 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593,
+ 50599, 50627, 50647, 50651, 50671, 50683, 50707, 50723, 50741, 50753,
+ 50767, 50773, 50777, 50789, 50821, 50833, 50839, 50849, 50857, 50867,
+ 50873, 50891, 50893, 50909, 50923, 50929, 50951, 50957, 50969, 50971,
+ 50989, 50993, 51001, 51031, 51043, 51047, 51059, 51061, 51071, 51109,
+ 51131, 51133, 51137, 51151, 51157, 51169, 51193, 51197, 51199, 51203,
+ 51217, 51229, 51239, 51241, 51257, 51263, 51283, 51287, 51307, 51329,
+ 51341, 51343, 51347, 51349, 51361, 51383, 51407, 51413, 51419, 51421,
+ 51427, 51431, 51437, 51439, 51449, 51461, 51473, 51479, 51481, 51487,
+ 51503, 51511, 51517, 51521, 51539, 51551, 51563, 51577, 51581, 51593,
+ 51599, 51607, 51613, 51631, 51637, 51647, 51659, 51673, 51679, 51683,
+ 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, 51797, 51803,
+ 51817, 51827, 51829, 51839, 51853, 51859, 51869, 51871, 51893, 51899,
+ 51907, 51913, 51929, 51941, 51949, 51971, 51973, 51977, 51991, 52009,
+ 52021, 52027, 52051, 52057, 52067, 52069, 52081, 52103, 52121, 52127,
+ 52147, 52153, 52163, 52177, 52181, 52183, 52189, 52201, 52223, 52237,
+ 52249, 52253, 52259, 52267, 52289, 52291, 52301, 52313, 52321, 52361,
+ 52363, 52369, 52379, 52387, 52391, 52433, 52453, 52457, 52489, 52501,
+ 52511, 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579,
+ 52583, 52609, 52627, 52631, 52639, 52667, 52673, 52691, 52697, 52709,
+ 52711, 52721, 52727, 52733, 52747, 52757, 52769, 52783, 52807, 52813,
+ 52817, 52837, 52859, 52861, 52879, 52883, 52889, 52901, 52903, 52919,
+ 52937, 52951, 52957, 52963, 52967, 52973, 52981, 52999, 53003, 53017,
+ 53047, 53051, 53069, 53077, 53087, 53089, 53093, 53101, 53113, 53117,
+ 53129, 53147, 53149, 53161, 53171, 53173, 53189, 53197, 53201, 53231,
+ 53233, 53239, 53267, 53269, 53279, 53281, 53299, 53309, 53323, 53327,
+ 53353, 53359, 53377, 53381, 53401, 53407, 53411, 53419, 53437, 53441,
+ 53453, 53479, 53503, 53507, 53527, 53549, 53551, 53569, 53591, 53593,
+ 53597, 53609, 53611, 53617, 53623, 53629, 53633, 53639, 53653, 53657,
+ 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, 53777, 53783,
+ 53791, 53813, 53819, 53831, 53849, 53857, 53861, 53881, 53887, 53891,
+ 53897, 53899, 53917, 53923, 53927, 53939, 53951, 53959, 53987, 53993,
+ 54001, 54011, 54013, 54037, 54049, 54059, 54083, 54091, 54101, 54121,
+ 54133, 54139, 54151, 54163, 54167, 54181, 54193, 54217, 54251, 54269,
+ 54277, 54287, 54293, 54311, 54319, 54323, 54331, 54347, 54361, 54367,
+ 54371, 54377, 54401, 54403, 54409, 54413, 54419, 54421, 54437, 54443,
+ 54449, 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541,
+ 54547, 54559, 54563, 54577, 54581, 54583, 54601, 54617, 54623, 54629,
+ 54631, 54647, 54667, 54673, 54679, 54709, 54713, 54721, 54727, 54751,
+ 54767, 54773, 54779, 54787, 54799, 54829, 54833, 54851, 54869, 54877,
+ 54881, 54907, 54917, 54919, 54941, 54949, 54959, 54973, 54979, 54983,
+ 55001, 55009, 55021, 55049, 55051, 55057, 55061, 55073, 55079, 55103,
+ 55109, 55117, 55127, 55147, 55163, 55171, 55201, 55207, 55213, 55217,
+ 55219, 55229, 55243, 55249, 55259, 55291, 55313, 55331, 55333, 55337,
+ 55339, 55343, 55351, 55373, 55381, 55399, 55411, 55439, 55441, 55457,
+ 55469, 55487, 55501, 55511, 55529, 55541, 55547, 55579, 55589, 55603,
+ 55609, 55619, 55621, 55631, 55633, 55639, 55661, 55663, 55667, 55673,
+ 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, 55787, 55793,
+ 55799, 55807, 55813, 55817, 55819, 55823, 55829, 55837, 55843, 55849,
+ 55871, 55889, 55897, 55901, 55903, 55921, 55927, 55931, 55933, 55949,
+ 55967, 55987, 55997, 56003, 56009, 56039, 56041, 56053, 56081, 56087,
+ 56093, 56099, 56101, 56113, 56123, 56131, 56149, 56167, 56171, 56179,
+ 56197, 56207, 56209, 56237, 56239, 56249, 56263, 56267, 56269, 56299,
+ 56311, 56333, 56359, 56369, 56377, 56383, 56393, 56401, 56417, 56431,
+ 56437, 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503,
+ 56509, 56519, 56527, 56531, 56533, 56543, 56569, 56591, 56597, 56599,
+ 56611, 56629, 56633, 56659, 56663, 56671, 56681, 56687, 56701, 56711,
+ 56713, 56731, 56737, 56747, 56767, 56773, 56779, 56783, 56807, 56809,
+ 56813, 56821, 56827, 56843, 56857, 56873, 56891, 56893, 56897, 56909,
+ 56911, 56921, 56923, 56929, 56941, 56951, 56957, 56963, 56983, 56989,
+ 56993, 56999, 57037, 57041, 57047, 57059, 57073, 57077, 57089, 57097,
+ 57107, 57119, 57131, 57139, 57143, 57149, 57163, 57173, 57179, 57191,
+ 57193, 57203, 57221, 57223, 57241, 57251, 57259, 57269, 57271, 57283,
+ 57287, 57301, 57329, 57331, 57347, 57349, 57367, 57373, 57383, 57389,
+ 57397, 57413, 57427, 57457, 57467, 57487, 57493, 57503, 57527, 57529,
+ 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, 57649, 57653,
+ 57667, 57679, 57689, 57697, 57709, 57713, 57719, 57727, 57731, 57737,
+ 57751, 57773, 57781, 57787, 57791, 57793, 57803, 57809, 57829, 57839,
+ 57847, 57853, 57859, 57881, 57899, 57901, 57917, 57923, 57943, 57947,
+ 57973, 57977, 57991, 58013, 58027, 58031, 58043, 58049, 58057, 58061,
+ 58067, 58073, 58099, 58109, 58111, 58129, 58147, 58151, 58153, 58169,
+ 58171, 58189, 58193, 58199, 58207, 58211, 58217, 58229, 58231, 58237,
+ 58243, 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379,
+ 58391, 58393, 58403, 58411, 58417, 58427, 58439, 58441, 58451, 58453,
+ 58477, 58481, 58511, 58537, 58543, 58549, 58567, 58573, 58579, 58601,
+ 58603, 58613, 58631, 58657, 58661, 58679, 58687, 58693, 58699, 58711,
+ 58727, 58733, 58741, 58757, 58763, 58771, 58787, 58789, 58831, 58889,
+ 58897, 58901, 58907, 58909, 58913, 58921, 58937, 58943, 58963, 58967,
+ 58979, 58991, 58997, 59009, 59011, 59021, 59023, 59029, 59051, 59053,
+ 59063, 59069, 59077, 59083, 59093, 59107, 59113, 59119, 59123, 59141,
+ 59149, 59159, 59167, 59183, 59197, 59207, 59209, 59219, 59221, 59233,
+ 59239, 59243, 59263, 59273, 59281, 59333, 59341, 59351, 59357, 59359,
+ 59369, 59377, 59387, 59393, 59399, 59407, 59417, 59419, 59441, 59443,
+ 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, 59539, 59557,
+ 59561, 59567, 59581, 59611, 59617, 59621, 59627, 59629, 59651, 59659,
+ 59663, 59669, 59671, 59693, 59699, 59707, 59723, 59729, 59743, 59747,
+ 59753, 59771, 59779, 59791, 59797, 59809, 59833, 59863, 59879, 59887,
+ 59921, 59929, 59951, 59957, 59971, 59981, 59999, 60013, 60017, 60029,
+ 60037, 60041, 60077, 60083, 60089, 60091, 60101, 60103, 60107, 60127,
+ 60133, 60139, 60149, 60161, 60167, 60169, 60209, 60217, 60223, 60251,
+ 60257, 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353,
+ 60373, 60383, 60397, 60413, 60427, 60443, 60449, 60457, 60493, 60497,
+ 60509, 60521, 60527, 60539, 60589, 60601, 60607, 60611, 60617, 60623,
+ 60631, 60637, 60647, 60649, 60659, 60661, 60679, 60689, 60703, 60719,
+ 60727, 60733, 60737, 60757, 60761, 60763, 60773, 60779, 60793, 60811,
+ 60821, 60859, 60869, 60887, 60889, 60899, 60901, 60913, 60917, 60919,
+ 60923, 60937, 60943, 60953, 60961, 61001, 61007, 61027, 61031, 61043,
+ 61051, 61057, 61091, 61099, 61121, 61129, 61141, 61151, 61153, 61169,
+ 61211, 61223, 61231, 61253, 61261, 61283, 61291, 61297, 61331, 61333,
+ 61339, 61343, 61357, 61363, 61379, 61381, 61403, 61409, 61417, 61441,
+ 61463, 61469, 61471, 61483, 61487, 61493, 61507, 61511, 61519, 61543,
+ 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, 61627, 61631,
+ 61637, 61643, 61651, 61657, 61667, 61673, 61681, 61687, 61703, 61717,
+ 61723, 61729, 61751, 61757, 61781, 61813, 61819, 61837, 61843, 61861,
+ 61871, 61879, 61909, 61927, 61933, 61949, 61961, 61967, 61979, 61981,
+ 61987, 61991, 62003, 62011, 62017, 62039, 62047, 62053, 62057, 62071,
+ 62081, 62099, 62119, 62129, 62131, 62137, 62141, 62143, 62171, 62189,
+ 62191, 62201, 62207, 62213, 62219, 62233, 62273, 62297, 62299, 62303,
+ 62311, 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459,
+ 62467, 62473, 62477, 62483, 62497, 62501, 62507, 62533, 62539, 62549,
+ 62563, 62581, 62591, 62597, 62603, 62617, 62627, 62633, 62639, 62653,
+ 62659, 62683, 62687, 62701, 62723, 62731, 62743, 62753, 62761, 62773,
+ 62791, 62801, 62819, 62827, 62851, 62861, 62869, 62873, 62897, 62903,
+ 62921, 62927, 62929, 62939, 62969, 62971, 62981, 62983, 62987, 62989,
+ 63029, 63031, 63059, 63067, 63073, 63079, 63097, 63103, 63113, 63127,
+ 63131, 63149, 63179, 63197, 63199, 63211, 63241, 63247, 63277, 63281,
+ 63299, 63311, 63313, 63317, 63331, 63337, 63347, 63353, 63361, 63367,
+ 63377, 63389, 63391, 63397, 63409, 63419, 63421, 63439, 63443, 63463,
+ 63467, 63473, 63487, 63493, 63499, 63521, 63527, 63533, 63541, 63559,
+ 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, 63629, 63647,
+ 63649, 63659, 63667, 63671, 63689, 63691, 63697, 63703, 63709, 63719,
+ 63727, 63737, 63743, 63761, 63773, 63781, 63793, 63799, 63803, 63809,
+ 63823, 63839, 63841, 63853, 63857, 63863, 63901, 63907, 63913, 63929,
+ 63949, 63977, 63997, 64007, 64013, 64019, 64033, 64037, 64063, 64067,
+ 64081, 64091, 64109, 64123, 64151, 64153, 64157, 64171, 64187, 64189,
+ 64217, 64223, 64231, 64237, 64271, 64279, 64283, 64301, 64303, 64319,
+ 64327, 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453,
+ 64483, 64489, 64499, 64513, 64553, 64567, 64577, 64579, 64591, 64601,
+ 64609, 64613, 64621, 64627, 64633, 64661, 64663, 64667, 64679, 64693,
+ 64709, 64717, 64747, 64763, 64781, 64783, 64793, 64811, 64817, 64849,
+ 64853, 64871, 64877, 64879, 64891, 64901, 64919, 64921, 64927, 64937,
+ 64951, 64969, 64997, 65003, 65011, 65027, 65029, 65033, 65053, 65063,
+ 65071, 65089, 65099, 65101, 65111, 65119, 65123, 65129, 65141, 65147,
+ 65167, 65171, 65173, 65179, 65183, 65203, 65213, 65239, 65257, 65267,
+ 65269, 65287, 65293, 65309, 65323, 65327, 65353, 65357, 65371, 65381,
+ 65393, 65407, 65413, 65419, 65423, 65437, 65447, 65449, 65479, 65497,
+ 65519, 65521, 65537, 65539, 65543, 65551, 65557, 65563, 65579, 65581,
+ 65587, 65599, 65609, 65617, 65629, 65633, 65647, 65651, 65657, 65677,
+ 65687, 65699, 65701, 65707, 65713, 65717, 65719, 65729, 65731, 65761,
+ 65777, 65789, 65809, 65827, 65831, 65837, 65839, 65843, 65851, 65867,
+ 65881, 65899, 65921, 65927, 65929, 65951, 65957, 65963, 65981, 65983,
+ 65993, 66029, 66037, 66041, 66047, 66067, 66071, 66083, 66089, 66103,
+ 66107, 66109, 66137, 66161, 66169, 66173, 66179, 66191, 66221, 66239,
+ 66271, 66293, 66301, 66337, 66343, 66347, 66359, 66361, 66373, 66377,
+ 66383, 66403, 66413, 66431, 66449, 66457, 66463, 66467, 66491, 66499,
+ 66509, 66523, 66529, 66533, 66541, 66553, 66569, 66571, 66587, 66593,
+ 66601, 66617, 66629, 66643, 66653, 66683, 66697, 66701, 66713, 66721,
+ 66733, 66739, 66749, 66751, 66763, 66791, 66797, 66809, 66821, 66841,
+ 66851, 66853, 66863, 66877, 66883, 66889, 66919, 66923, 66931, 66943,
+ 66947, 66949, 66959, 66973, 66977, 67003, 67021, 67033, 67043, 67049,
+ 67057, 67061, 67073, 67079, 67103, 67121, 67129, 67139, 67141, 67153,
+ 67157, 67169, 67181, 67187, 67189, 67211, 67213, 67217, 67219, 67231,
+ 67247, 67261, 67271, 67273, 67289, 67307, 67339, 67343, 67349, 67369,
+ 67391, 67399, 67409, 67411, 67421, 67427, 67429, 67433, 67447, 67453,
+ 67477, 67481, 67489, 67493, 67499, 67511, 67523, 67531, 67537, 67547,
+ 67559, 67567, 67577, 67579, 67589, 67601, 67607, 67619, 67631, 67651,
+ 67679, 67699, 67709, 67723, 67733, 67741, 67751, 67757, 67759, 67763,
+ 67777, 67783, 67789, 67801, 67807, 67819, 67829, 67843, 67853, 67867,
+ 67883, 67891, 67901, 67927, 67931, 67933, 67939, 67943, 67957, 67961,
+ 67967, 67979, 67987, 67993, 68023, 68041, 68053, 68059, 68071, 68087,
+ 68099, 68111, 68113, 68141, 68147, 68161, 68171, 68207, 68209, 68213,
+ 68219, 68227, 68239, 68261, 68279, 68281, 68311, 68329, 68351, 68371,
+ 68389, 68399, 68437, 68443, 68447, 68449, 68473, 68477, 68483, 68489,
+ 68491, 68501, 68507, 68521, 68531, 68539, 68543, 68567, 68581, 68597,
+ 68611, 68633, 68639, 68659, 68669, 68683, 68687, 68699, 68711, 68713,
+ 68729, 68737, 68743, 68749, 68767, 68771, 68777, 68791, 68813, 68819,
+ 68821, 68863, 68879, 68881, 68891, 68897, 68899, 68903, 68909, 68917,
+ 68927, 68947, 68963, 68993, 69001, 69011, 69019, 69029, 69031, 69061,
+ 69067, 69073, 69109, 69119, 69127, 69143, 69149, 69151, 69163, 69191,
+ 69193, 69197, 69203, 69221, 69233, 69239, 69247, 69257, 69259, 69263,
+ 69313, 69317, 69337, 69341, 69371, 69379, 69383, 69389, 69401, 69403,
+ 69427, 69431, 69439, 69457, 69463, 69467, 69473, 69481, 69491, 69493,
+ 69497, 69499, 69539, 69557, 69593, 69623, 69653, 69661, 69677, 69691,
+ 69697, 69709, 69737, 69739, 69761, 69763, 69767, 69779, 69809, 69821,
+ 69827, 69829, 69833, 69847, 69857, 69859, 69877, 69899, 69911, 69929,
+ 69931, 69941, 69959, 69991, 69997, 70001, 70003, 70009, 70019, 70039,
+ 70051, 70061, 70067, 70079, 70099, 70111, 70117, 70121, 70123, 70139,
+ 70141, 70157, 70163, 70177, 70181, 70183, 70199, 70201, 70207, 70223,
+ 70229, 70237, 70241, 70249, 70271, 70289, 70297, 70309, 70313, 70321,
+ 70327, 70351, 70373, 70379, 70381, 70393, 70423, 70429, 70439, 70451,
+ 70457, 70459, 70481, 70487, 70489, 70501, 70507, 70529, 70537, 70549,
+ 70571, 70573, 70583, 70589, 70607, 70619, 70621, 70627, 70639, 70657,
+ 70663, 70667, 70687, 70709, 70717, 70729, 70753, 70769, 70783, 70793,
+ 70823, 70841, 70843, 70849, 70853, 70867, 70877, 70879, 70891, 70901,
+ 70913, 70919, 70921, 70937, 70949, 70951, 70957, 70969, 70979, 70981,
+ 70991, 70997, 70999, 71011, 71023, 71039, 71059, 71069, 71081, 71089,
+ 71119, 71129, 71143, 71147, 71153, 71161, 71167, 71171, 71191, 71209,
+ 71233, 71237, 71249, 71257, 71261, 71263, 71287, 71293, 71317, 71327,
+ 71329, 71333, 71339, 71341, 71347, 71353, 71359, 71363, 71387, 71389,
+ 71399, 71411, 71413, 71419, 71429, 71437, 71443, 71453, 71471, 71473,
+ 71479, 71483, 71503, 71527, 71537, 71549, 71551, 71563, 71569, 71593,
+ 71597, 71633, 71647, 71663, 71671, 71693, 71699, 71707, 71711, 71713,
+ 71719, 71741, 71761, 71777, 71789, 71807, 71809, 71821, 71837, 71843,
+ 71849, 71861, 71867, 71879, 71881, 71887, 71899, 71909, 71917, 71933,
+ 71941, 71947, 71963, 71971, 71983, 71987, 71993, 71999, 72019, 72031,
+ 72043, 72047, 72053, 72073, 72077, 72089, 72091, 72101, 72103, 72109,
+ 72139, 72161, 72167, 72169, 72173, 72211, 72221, 72223, 72227, 72229,
+ 72251, 72253, 72269, 72271, 72277, 72287, 72307, 72313, 72337, 72341,
+ 72353, 72367, 72379, 72383, 72421, 72431, 72461, 72467, 72469, 72481,
+ 72493, 72497, 72503, 72533, 72547, 72551, 72559, 72577, 72613, 72617,
+ 72623, 72643, 72647, 72649, 72661, 72671, 72673, 72679, 72689, 72701,
+ 72707, 72719, 72727, 72733, 72739, 72763, 72767, 72797, 72817, 72823,
+ 72859, 72869, 72871, 72883, 72889, 72893, 72901, 72907, 72911, 72923,
+ 72931, 72937, 72949, 72953, 72959, 72973, 72977, 72997, 73009, 73013,
+ 73019, 73037, 73039, 73043, 73061, 73063, 73079, 73091, 73121, 73127,
+ 73133, 73141, 73181, 73189, 73237, 73243, 73259, 73277, 73291, 73303,
+ 73309, 73327, 73331, 73351, 73361, 73363, 73369, 73379, 73387, 73417,
+ 73421, 73433, 73453, 73459, 73471, 73477, 73483, 73517, 73523, 73529,
+ 73547, 73553, 73561, 73571, 73583, 73589, 73597, 73607, 73609, 73613,
+ 73637, 73643, 73651, 73673, 73679, 73681, 73693, 73699, 73709, 73721,
+ 73727, 73751, 73757, 73771, 73783, 73819, 73823, 73847, 73849, 73859,
+ 73867, 73877, 73883, 73897, 73907, 73939, 73943, 73951, 73961, 73973,
+ 73999, 74017, 74021, 74027, 74047, 74051, 74071, 74077, 74093, 74099,
+ 74101, 74131, 74143, 74149, 74159, 74161, 74167, 74177, 74189, 74197,
+ 74201, 74203, 74209, 74219, 74231, 74257, 74279, 74287, 74293, 74297,
+ 74311, 74317, 74323, 74353, 74357, 74363, 74377, 74381, 74383, 74411,
+ 74413, 74419, 74441, 74449, 74453, 74471, 74489, 74507, 74509, 74521,
+ 74527, 74531, 74551, 74561, 74567, 74573, 74587, 74597, 74609, 74611,
+ 74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, 74729, 74731,
+ 74747, 74759, 74761, 74771, 74779, 74797, 74821, 74827, 74831, 74843,
+ 74857, 74861, 74869, 74873, 74887, 74891, 74897, 74903, 74923, 74929,
+ 74933, 74941, 74959, 75011, 75013, 75017, 75029, 75037, 75041, 75079,
+ 75083, 75109, 75133, 75149, 75161, 75167, 75169, 75181, 75193, 75209,
+ 75211, 75217, 75223, 75227, 75239, 75253, 75269, 75277, 75289, 75307,
+ 75323, 75329, 75337, 75347, 75353, 75367, 75377, 75389, 75391, 75401,
+ 75403, 75407, 75431, 75437, 75479, 75503, 75511, 75521, 75527, 75533,
+ 75539, 75541, 75553, 75557, 75571, 75577, 75583, 75611, 75617, 75619,
+ 75629, 75641, 75653, 75659, 75679, 75683, 75689, 75703, 75707, 75709,
+ 75721, 75731, 75743, 75767, 75773, 75781, 75787, 75793, 75797, 75821,
+ 75833, 75853, 75869, 75883, 75913, 75931, 75937, 75941, 75967, 75979,
+ 75983, 75989, 75991, 75997, 76001, 76003, 76031, 76039, 76079, 76081,
+ 76091, 76099, 76103, 76123, 76129, 76147, 76157, 76159, 76163, 76207,
+ 76213, 76231, 76243, 76249, 76253, 76259, 76261, 76283, 76289, 76303,
+ 76333, 76343, 76367, 76369, 76379, 76387, 76403, 76421, 76423, 76441,
+ 76463, 76471, 76481, 76487, 76493, 76507, 76511, 76519, 76537, 76541,
+ 76543, 76561, 76579, 76597, 76603, 76607, 76631, 76649, 76651, 76667,
+ 76673, 76679, 76697, 76717, 76733, 76753, 76757, 76771, 76777, 76781,
+ 76801, 76819, 76829, 76831, 76837, 76847, 76871, 76873, 76883, 76907,
+ 76913, 76919, 76943, 76949, 76961, 76963, 76991, 77003, 77017, 77023,
+ 77029, 77041, 77047, 77069, 77081, 77093, 77101, 77137, 77141, 77153,
+ 77167, 77171, 77191, 77201, 77213, 77237, 77239, 77243, 77249, 77261,
+ 77263, 77267, 77269, 77279, 77291, 77317, 77323, 77339, 77347, 77351,
+ 77359, 77369, 77377, 77383, 77417, 77419, 77431, 77447, 77471, 77477,
+ 77479, 77489, 77491, 77509, 77513, 77521, 77527, 77543, 77549, 77551,
+ 77557, 77563, 77569, 77573, 77587, 77591, 77611, 77617, 77621, 77641,
+ 77647, 77659, 77681, 77687, 77689, 77699, 77711, 77713, 77719, 77723,
+ 77731, 77743, 77747, 77761, 77773, 77783, 77797, 77801, 77813, 77839,
+ 77849, 77863, 77867, 77893, 77899, 77929, 77933, 77951, 77969, 77977,
+ 77983, 77999, 78007, 78017, 78031, 78041, 78049, 78059, 78079, 78101,
+ 78121, 78137, 78139, 78157, 78163, 78167, 78173, 78179, 78191, 78193,
+ 78203, 78229, 78233, 78241, 78259, 78277, 78283, 78301, 78307, 78311,
+ 78317, 78341, 78347, 78367, 78401, 78427, 78437, 78439, 78467, 78479,
+ 78487, 78497, 78509, 78511, 78517, 78539, 78541, 78553, 78569, 78571,
+ 78577, 78583, 78593, 78607, 78623, 78643, 78649, 78653, 78691, 78697,
+ 78707, 78713, 78721, 78737, 78779, 78781, 78787, 78791, 78797, 78803,
+ 78809, 78823, 78839, 78853, 78857, 78877, 78887, 78889, 78893, 78901,
+ 78919, 78929, 78941, 78977, 78979, 78989, 79031, 79039, 79043, 79063,
+ 79087, 79103, 79111, 79133, 79139, 79147, 79151, 79153, 79159, 79181,
+ 79187, 79193, 79201, 79229, 79231, 79241, 79259, 79273, 79279, 79283,
+ 79301, 79309, 79319, 79333, 79337, 79349, 79357, 79367, 79379, 79393,
+ 79397, 79399, 79411, 79423, 79427, 79433, 79451, 79481, 79493, 79531,
+ 79537, 79549, 79559, 79561, 79579, 79589, 79601, 79609, 79613, 79621,
+ 79627, 79631, 79633, 79657, 79669, 79687, 79691, 79693, 79697, 79699,
+ 79757, 79769, 79777, 79801, 79811, 79813, 79817, 79823, 79829, 79841,
+ 79843, 79847, 79861, 79867, 79873, 79889, 79901, 79903, 79907, 79939,
+ 79943, 79967, 79973, 79979, 79987, 79997, 79999, 80021, 80039, 80051,
+ 80071, 80077, 80107, 80111, 80141, 80147, 80149, 80153, 80167, 80173,
+ 80177, 80191, 80207, 80209, 80221, 80231, 80233, 80239, 80251, 80263,
+ 80273, 80279, 80287, 80309, 80317, 80329, 80341, 80347, 80363, 80369,
+ 80387, 80407, 80429, 80447, 80449, 80471, 80473, 80489, 80491, 80513,
+ 80527, 80537, 80557, 80567, 80599, 80603, 80611, 80621, 80627, 80629,
+ 80651, 80657, 80669, 80671, 80677, 80681, 80683, 80687, 80701, 80713,
+ 80737, 80747, 80749, 80761, 80777, 80779, 80783, 80789, 80803, 80809,
+ 80819, 80831, 80833, 80849, 80863, 80897, 80909, 80911, 80917, 80923,
+ 80929, 80933, 80953, 80963, 80989, 81001, 81013, 81017, 81019, 81023,
+ 81031, 81041, 81043, 81047, 81049, 81071, 81077, 81083, 81097, 81101,
+ 81119, 81131, 81157, 81163, 81173, 81181, 81197, 81199, 81203, 81223,
+ 81233, 81239, 81281, 81283, 81293, 81299, 81307, 81331, 81343, 81349,
+ 81353, 81359, 81371, 81373, 81401, 81409, 81421, 81439, 81457, 81463,
+ 81509, 81517, 81527, 81533, 81547, 81551, 81553, 81559, 81563, 81569,
+ 81611, 81619, 81629, 81637, 81647, 81649, 81667, 81671, 81677, 81689,
+ 81701, 81703, 81707, 81727, 81737, 81749, 81761, 81769, 81773, 81799,
+ 81817, 81839, 81847, 81853, 81869, 81883, 81899, 81901, 81919, 81929,
+ 81931, 81937, 81943, 81953, 81967, 81971, 81973, 82003, 82007, 82009,
+ 82013, 82021, 82031, 82037, 82039, 82051, 82067, 82073, 82129, 82139,
+ 82141, 82153, 82163, 82171, 82183, 82189, 82193, 82207, 82217, 82219,
+ 82223, 82231, 82237, 82241, 82261, 82267, 82279, 82301, 82307, 82339,
+ 82349, 82351, 82361, 82373, 82387, 82393, 82421, 82457, 82463, 82469,
+ 82471, 82483, 82487, 82493, 82499, 82507, 82529, 82531, 82549, 82559,
+ 82561, 82567, 82571, 82591, 82601, 82609, 82613, 82619, 82633, 82651,
+ 82657, 82699, 82721, 82723, 82727, 82729, 82757, 82759, 82763, 82781,
+ 82787, 82793, 82799, 82811, 82813, 82837, 82847, 82883, 82889, 82891,
+ 82903, 82913, 82939, 82963, 82981, 82997, 83003, 83009, 83023, 83047,
+ 83059, 83063, 83071, 83077, 83089, 83093, 83101, 83117, 83137, 83177,
+ 83203, 83207, 83219, 83221, 83227, 83231, 83233, 83243, 83257, 83267,
+ 83269, 83273, 83299, 83311, 83339, 83341, 83357, 83383, 83389, 83399,
+ 83401, 83407, 83417, 83423, 83431, 83437, 83443, 83449, 83459, 83471,
+ 83477, 83497, 83537, 83557, 83561, 83563, 83579, 83591, 83597, 83609,
+ 83617, 83621, 83639, 83641, 83653, 83663, 83689, 83701, 83717, 83719,
+ 83737, 83761, 83773, 83777, 83791, 83813, 83833, 83843, 83857, 83869,
+ 83873, 83891, 83903, 83911, 83921, 83933, 83939, 83969, 83983, 83987,
+ 84011, 84017, 84047, 84053, 84059, 84061, 84067, 84089, 84121, 84127,
+ 84131, 84137, 84143, 84163, 84179, 84181, 84191, 84199, 84211, 84221,
+ 84223, 84229, 84239, 84247, 84263, 84299, 84307, 84313, 84317, 84319,
+ 84347, 84349, 84377, 84389, 84391, 84401, 84407, 84421, 84431, 84437,
+ 84443, 84449, 84457, 84463, 84467, 84481, 84499, 84503, 84509, 84521,
+ 84523, 84533, 84551, 84559, 84589, 84629, 84631, 84649, 84653, 84659,
+ 84673, 84691, 84697, 84701, 84713, 84719, 84731, 84737, 84751, 84761,
+ 84787, 84793, 84809, 84811, 84827, 84857, 84859, 84869, 84871, 84913,
+ 84919, 84947, 84961, 84967, 84977, 84979, 84991, 85009, 85021, 85027,
+ 85037, 85049, 85061, 85081, 85087, 85091, 85093, 85103, 85109, 85121,
+ 85133, 85147, 85159, 85193, 85199, 85201, 85213, 85223, 85229, 85237,
+ 85243, 85247, 85259, 85297, 85303, 85313, 85331, 85333, 85361, 85363,
+ 85369, 85381, 85411, 85427, 85429, 85439, 85447, 85451, 85453, 85469,
+ 85487, 85513, 85517, 85523, 85531, 85549, 85571, 85577, 85597, 85601,
+ 85607, 85619, 85621, 85627, 85639, 85643, 85661, 85667, 85669, 85691,
+ 85703, 85711, 85717, 85733, 85751, 85781, 85793, 85817, 85819, 85829,
+ 85831, 85837, 85843, 85847, 85853, 85889, 85903, 85909, 85931, 85933,
+ 85991, 85999, 86011, 86017, 86027, 86029, 86069, 86077, 86083, 86111,
+ 86113, 86117, 86131, 86137, 86143, 86161, 86171, 86179, 86183, 86197,
+ 86201, 86209, 86239, 86243, 86249, 86257, 86263, 86269, 86287, 86291,
+ 86293, 86297, 86311, 86323, 86341, 86351, 86353, 86357, 86369, 86371,
+ 86381, 86389, 86399, 86413, 86423, 86441, 86453, 86461, 86467, 86477,
+ 86491, 86501, 86509, 86531, 86533, 86539, 86561, 86573, 86579, 86587,
+ 86599, 86627, 86629, 86677, 86689, 86693, 86711, 86719, 86729, 86743,
+ 86753, 86767, 86771, 86783, 86813, 86837, 86843, 86851, 86857, 86861,
+ 86869, 86923, 86927, 86929, 86939, 86951, 86959, 86969, 86981, 86993,
+ 87011, 87013, 87037, 87041, 87049, 87071, 87083, 87103, 87107, 87119,
+ 87121, 87133, 87149, 87151, 87179, 87181, 87187, 87211, 87221, 87223,
+ 87251, 87253, 87257, 87277, 87281, 87293, 87299, 87313, 87317, 87323,
+ 87337, 87359, 87383, 87403, 87407, 87421, 87427, 87433, 87443, 87473,
+ 87481, 87491, 87509, 87511, 87517, 87523, 87539, 87541, 87547, 87553,
+ 87557, 87559, 87583, 87587, 87589, 87613, 87623, 87629, 87631, 87641,
+ 87643, 87649, 87671, 87679, 87683, 87691, 87697, 87701, 87719, 87721,
+ 87739, 87743, 87751, 87767, 87793, 87797, 87803, 87811, 87833, 87853,
+ 87869, 87877, 87881, 87887, 87911, 87917, 87931, 87943, 87959, 87961,
+ 87973, 87977, 87991, 88001, 88003, 88007, 88019, 88037, 88069, 88079,
+ 88093, 88117, 88129, 88169, 88177, 88211, 88223, 88237, 88241, 88259,
+ 88261, 88289, 88301, 88321, 88327, 88337, 88339, 88379, 88397, 88411,
+ 88423, 88427, 88463, 88469, 88471, 88493, 88499, 88513, 88523, 88547,
+ 88589, 88591, 88607, 88609, 88643, 88651, 88657, 88661, 88663, 88667,
+ 88681, 88721, 88729, 88741, 88747, 88771, 88789, 88793, 88799, 88801,
+ 88807, 88811, 88813, 88817, 88819, 88843, 88853, 88861, 88867, 88873,
+ 88883, 88897, 88903, 88919, 88937, 88951, 88969, 88993, 88997, 89003,
+ 89009, 89017, 89021, 89041, 89051, 89057, 89069, 89071, 89083, 89087,
+ 89101, 89107, 89113, 89119, 89123, 89137, 89153, 89189, 89203, 89209,
+ 89213, 89227, 89231, 89237, 89261, 89269, 89273, 89293, 89303, 89317,
+ 89329, 89363, 89371, 89381, 89387, 89393, 89399, 89413, 89417, 89431,
+ 89443, 89449, 89459, 89477, 89491, 89501, 89513, 89519, 89521, 89527,
+ 89533, 89561, 89563, 89567, 89591, 89597, 89599, 89603, 89611, 89627,
+ 89633, 89653, 89657, 89659, 89669, 89671, 89681, 89689, 89753, 89759,
+ 89767, 89779, 89783, 89797, 89809, 89819, 89821, 89833, 89839, 89849,
+ 89867, 89891, 89897, 89899, 89909, 89917, 89923, 89939, 89959, 89963,
+ 89977, 89983, 89989, 90001, 90007, 90011, 90017, 90019, 90023, 90031,
+ 90053, 90059, 90067, 90071, 90073, 90089, 90107, 90121, 90127, 90149,
+ 90163, 90173, 90187, 90191, 90197, 90199, 90203, 90217, 90227, 90239,
+ 90247, 90263, 90271, 90281, 90289, 90313, 90353, 90359, 90371, 90373,
+ 90379, 90397, 90401, 90403, 90407, 90437, 90439, 90469, 90473, 90481,
+ 90499, 90511, 90523, 90527, 90529, 90533, 90547, 90583, 90599, 90617,
+ 90619, 90631, 90641, 90647, 90659, 90677, 90679, 90697, 90703, 90709,
+ 90731, 90749, 90787, 90793, 90803, 90821, 90823, 90833, 90841, 90847,
+ 90863, 90887, 90901, 90907, 90911, 90917, 90931, 90947, 90971, 90977,
+ 90989, 90997, 91009, 91019, 91033, 91079, 91081, 91097, 91099, 91121,
+ 91127, 91129, 91139, 91141, 91151, 91153, 91159, 91163, 91183, 91193,
+ 91199, 91229, 91237, 91243, 91249, 91253, 91283, 91291, 91297, 91303,
+ 91309, 91331, 91367, 91369, 91373, 91381, 91387, 91393, 91397, 91411,
+ 91423, 91433, 91453, 91457, 91459, 91463, 91493, 91499, 91513, 91529,
+ 91541, 91571, 91573, 91577, 91583, 91591, 91621, 91631, 91639, 91673,
+ 91691, 91703, 91711, 91733, 91753, 91757, 91771, 91781, 91801, 91807,
+ 91811, 91813, 91823, 91837, 91841, 91867, 91873, 91909, 91921, 91939,
+ 91943, 91951, 91957, 91961, 91967, 91969, 91997, 92003, 92009, 92033,
+ 92041, 92051, 92077, 92083, 92107, 92111, 92119, 92143, 92153, 92173,
+ 92177, 92179, 92189, 92203, 92219, 92221, 92227, 92233, 92237, 92243,
+ 92251, 92269, 92297, 92311, 92317, 92333, 92347, 92353, 92357, 92363,
+ 92369, 92377, 92381, 92383, 92387, 92399, 92401, 92413, 92419, 92431,
+ 92459, 92461, 92467, 92479, 92489, 92503, 92507, 92551, 92557, 92567,
+ 92569, 92581, 92593, 92623, 92627, 92639, 92641, 92647, 92657, 92669,
+ 92671, 92681, 92683, 92693, 92699, 92707, 92717, 92723, 92737, 92753,
+ 92761, 92767, 92779, 92789, 92791, 92801, 92809, 92821, 92831, 92849,
+ 92857, 92861, 92863, 92867, 92893, 92899, 92921, 92927, 92941, 92951,
+ 92957, 92959, 92987, 92993, 93001, 93047, 93053, 93059, 93077, 93083,
+ 93089, 93097, 93103, 93113, 93131, 93133, 93139, 93151, 93169, 93179,
+ 93187, 93199, 93229, 93239, 93241, 93251, 93253, 93257, 93263, 93281,
+ 93283, 93287, 93307, 93319, 93323, 93329, 93337, 93371, 93377, 93383,
+ 93407, 93419, 93427, 93463, 93479, 93481, 93487, 93491, 93493, 93497,
+ 93503, 93523, 93529, 93553, 93557, 93559, 93563, 93581, 93601, 93607,
+ 93629, 93637, 93683, 93701, 93703, 93719, 93739, 93761, 93763, 93787,
+ 93809, 93811, 93827, 93851, 93871, 93887, 93889, 93893, 93901, 93911,
+ 93913, 93923, 93937, 93941, 93949, 93967, 93971, 93979, 93983, 93997,
+ 94007, 94009, 94033, 94049, 94057, 94063, 94079, 94099, 94109, 94111,
+ 94117, 94121, 94151, 94153, 94169, 94201, 94207, 94219, 94229, 94253,
+ 94261, 94273, 94291, 94307, 94309, 94321, 94327, 94331, 94343, 94349,
+ 94351, 94379, 94397, 94399, 94421, 94427, 94433, 94439, 94441, 94447,
+ 94463, 94477, 94483, 94513, 94529, 94531, 94541, 94543, 94547, 94559,
+ 94561, 94573, 94583, 94597, 94603, 94613, 94621, 94649, 94651, 94687,
+ 94693, 94709, 94723, 94727, 94747, 94771, 94777, 94781, 94789, 94793,
+ 94811, 94819, 94823, 94837, 94841, 94847, 94849, 94873, 94889, 94903,
+ 94907, 94933, 94949, 94951, 94961, 94993, 94999, 95003, 95009, 95021,
+ 95027, 95063, 95071, 95083, 95087, 95089, 95093, 95101, 95107, 95111,
+ 95131, 95143, 95153, 95177, 95189, 95191, 95203, 95213, 95219, 95231,
+ 95233, 95239, 95257, 95261, 95267, 95273, 95279, 95287, 95311, 95317,
+ 95327, 95339, 95369, 95383, 95393, 95401, 95413, 95419, 95429, 95441,
+ 95443, 95461, 95467, 95471, 95479, 95483, 95507, 95527, 95531, 95539,
+ 95549, 95561, 95569, 95581, 95597, 95603, 95617, 95621, 95629, 95633,
+ 95651, 95701, 95707, 95713, 95717, 95723, 95731, 95737, 95747, 95773,
+ 95783, 95789, 95791, 95801, 95803, 95813, 95819, 95857, 95869, 95873,
+ 95881, 95891, 95911, 95917, 95923, 95929, 95947, 95957, 95959, 95971,
+ 95987, 95989, 96001, 96013, 96017, 96043, 96053, 96059, 96079, 96097,
+ 96137, 96149, 96157, 96167, 96179, 96181, 96199, 96211, 96221, 96223,
+ 96233, 96259, 96263, 96269, 96281, 96289, 96293, 96323, 96329, 96331,
+ 96337, 96353, 96377, 96401, 96419, 96431, 96443, 96451, 96457, 96461,
+ 96469, 96479, 96487, 96493, 96497, 96517, 96527, 96553, 96557, 96581,
+ 96587, 96589, 96601, 96643, 96661, 96667, 96671, 96697, 96703, 96731,
+ 96737, 96739, 96749, 96757, 96763, 96769, 96779, 96787, 96797, 96799,
+ 96821, 96823, 96827, 96847, 96851, 96857, 96893, 96907, 96911, 96931,
+ 96953, 96959, 96973, 96979, 96989, 96997, 97001, 97003, 97007, 97021,
+ 97039, 97073, 97081, 97103, 97117, 97127, 97151, 97157, 97159, 97169,
+ 97171, 97177, 97187, 97213, 97231, 97241, 97259, 97283, 97301, 97303,
+ 97327, 97367, 97369, 97373, 97379, 97381, 97387, 97397, 97423, 97429,
+ 97441, 97453, 97459, 97463, 97499, 97501, 97511, 97523, 97547, 97549,
+ 97553, 97561, 97571, 97577, 97579, 97583, 97607, 97609, 97613, 97649,
+ 97651, 97673, 97687, 97711, 97729, 97771, 97777, 97787, 97789, 97813,
+ 97829, 97841, 97843, 97847, 97849, 97859, 97861, 97871, 97879, 97883,
+ 97919, 97927, 97931, 97943, 97961, 97967, 97973, 97987, 98009, 98011,
+ 98017, 98041, 98047, 98057, 98081, 98101, 98123, 98129, 98143, 98179,
+ 98207, 98213, 98221, 98227, 98251, 98257, 98269, 98297, 98299, 98317,
+ 98321, 98323, 98327, 98347, 98369, 98377, 98387, 98389, 98407, 98411,
+ 98419, 98429, 98443, 98453, 98459, 98467, 98473, 98479, 98491, 98507,
+ 98519, 98533, 98543, 98561, 98563, 98573, 98597, 98621, 98627, 98639,
+ 98641, 98663, 98669, 98689, 98711, 98713, 98717, 98729, 98731, 98737,
+ 98773, 98779, 98801, 98807, 98809, 98837, 98849, 98867, 98869, 98873,
+ 98887, 98893, 98897, 98899, 98909, 98911, 98927, 98929, 98939, 98947,
+ 98953, 98963, 98981, 98993, 98999, 99013, 99017, 99023, 99041, 99053,
+ 99079, 99083, 99089, 99103, 99109, 99119, 99131, 99133, 99137, 99139,
+ 99149, 99173, 99181, 99191, 99223, 99233, 99241, 99251, 99257, 99259,
+ 99277, 99289, 99317, 99347, 99349, 99367, 99371, 99377, 99391, 99397,
+ 99401, 99409, 99431, 99439, 99469, 99487, 99497, 99523, 99527, 99529,
+ 99551, 99559, 99563, 99571, 99577, 99581, 99607, 99611, 99623, 99643,
+ 99661, 99667, 99679, 99689, 99707, 99709, 99713, 99719, 99721, 99733,
+ 99761, 99767, 99787, 99793, 99809, 99817, 99823, 99829, 99833, 99839,
+ 99859, 99871, 99877, 99881, 99901, 99907, 99923, 99929, 99961, 99971,
+ 99989, 99991, 100003, 100019, 100043, 100049, 100057, 100069, 100103, 100109,
+100129, 100151, 100153, 100169, 100183, 100189, 100193, 100207, 100213, 100237,
+100267, 100271, 100279, 100291, 100297, 100313, 100333, 100343, 100357, 100361,
+100363, 100379, 100391, 100393, 100403, 100411, 100417, 100447, 100459, 100469,
+100483, 100493, 100501, 100511, 100517, 100519, 100523, 100537, 100547, 100549,
+100559, 100591, 100609, 100613, 100621, 100649, 100669, 100673, 100693, 100699,
+100703, 100733, 100741, 100747, 100769, 100787, 100799, 100801, 100811, 100823,
+100829, 100847, 100853, 100907, 100913, 100927, 100931, 100937, 100943, 100957,
+100981, 100987, 100999, 101009, 101021, 101027, 101051, 101063, 101081, 101089,
+101107, 101111, 101113, 101117, 101119, 101141, 101149, 101159, 101161, 101173,
+101183, 101197, 101203, 101207, 101209, 101221, 101267, 101273, 101279, 101281,
+101287, 101293, 101323, 101333, 101341, 101347, 101359, 101363, 101377, 101383,
+101399, 101411, 101419, 101429, 101449, 101467, 101477, 101483, 101489, 101501,
+101503, 101513, 101527, 101531, 101533, 101537, 101561, 101573, 101581, 101599,
+101603, 101611, 101627, 101641, 101653, 101663, 101681, 101693, 101701, 101719,
+101723, 101737, 101741, 101747, 101749, 101771, 101789, 101797, 101807, 101833,
+101837, 101839, 101863, 101869, 101873, 101879, 101891, 101917, 101921, 101929,
+101939, 101957, 101963, 101977, 101987, 101999, 102001, 102013, 102019, 102023,
+102031, 102043, 102059, 102061, 102071, 102077, 102079, 102101, 102103, 102107,
+102121, 102139, 102149, 102161, 102181, 102191, 102197, 102199, 102203, 102217,
+102229, 102233, 102241, 102251, 102253, 102259, 102293, 102299, 102301, 102317,
+102329, 102337, 102359, 102367, 102397, 102407, 102409, 102433, 102437, 102451,
+102461, 102481, 102497, 102499, 102503, 102523, 102533, 102539, 102547, 102551,
+102559, 102563, 102587, 102593, 102607, 102611, 102643, 102647, 102653, 102667,
+102673, 102677, 102679, 102701, 102761, 102763, 102769, 102793, 102797, 102811,
+102829, 102841, 102859, 102871, 102877, 102881, 102911, 102913, 102929, 102931,
+102953, 102967, 102983, 103001, 103007, 103043, 103049, 103067, 103069, 103079,
+103087, 103091, 103093, 103099, 103123, 103141, 103171, 103177, 103183, 103217,
+103231, 103237, 103289, 103291, 103307, 103319, 103333, 103349, 103357, 103387,
+103391, 103393, 103399, 103409, 103421, 103423, 103451, 103457, 103471, 103483,
+103511, 103529, 103549, 103553, 103561, 103567, 103573, 103577, 103583, 103591,
+103613, 103619, 103643, 103651, 103657, 103669, 103681, 103687, 103699, 103703,
+103723, 103769, 103787, 103801, 103811, 103813, 103837, 103841, 103843, 103867,
+103889, 103903, 103913, 103919, 103951, 103963, 103967, 103969, 103979, 103981,
+103991, 103993, 103997, 104003, 104009, 104021, 104033, 104047, 104053, 104059,
+104087, 104089, 104107, 104113, 104119, 104123, 104147, 104149, 104161, 104173,
+104179, 104183, 104207, 104231, 104233, 104239, 104243, 104281, 104287, 104297,
+104309, 104311, 104323, 104327, 104347, 104369, 104381, 104383, 104393, 104399,
+104417, 104459, 104471, 104473, 104479, 104491, 104513, 104527, 104537, 104543,
+104549, 104551, 104561, 104579, 104593, 104597, 104623, 104639, 104651, 104659,
+104677, 104681, 104683, 104693, 104701, 104707, 104711, 104717, 104723, 104729,
+)
diff --git a/lib/Python/Lib/Crypto/Util/py21compat.py b/lib/Python/Lib/Crypto/Util/py21compat.py
new file mode 100644
index 000000000..624408bd2
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/py21compat.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+#
+# Util/py21compat.py : Compatibility code for Python 2.1
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Compatibility code for Python 2.1
+
+Currently, this just defines:
+ - True and False
+ - object
+ - isinstance
+"""
+
+__revision__ = "$Id$"
+__all__ = []
+
+import sys
+import __builtin__
+
+# 'True' and 'False' aren't defined in Python 2.1. Define them.
+try:
+ True, False
+except NameError:
+ (True, False) = (1, 0)
+ __all__ += ['True', 'False']
+
+# New-style classes were introduced in Python 2.2. Defining "object" in Python
+# 2.1 lets us use new-style classes in versions of Python that support them,
+# while still maintaining backward compatibility with old-style classes
+try:
+ object
+except NameError:
+ class object: pass
+ __all__ += ['object']
+
+# Starting with Python 2.2, isinstance allows a tuple for the second argument.
+# Also, builtins like "tuple", "list", "str", "unicode", "int", and "long"
+# became first-class types, rather than functions. We want to support
+# constructs like:
+# isinstance(x, (int, long))
+# So we hack it for Python 2.1.
+try:
+ isinstance(5, (int, long))
+except TypeError:
+ __all__ += ['isinstance']
+ _builtin_type_map = {
+ tuple: type(()),
+ list: type([]),
+ str: type(""),
+ unicode: type(u""),
+ int: type(0),
+ long: type(0L),
+ }
+ def isinstance(obj, t):
+ if not __builtin__.isinstance(t, type(())):
+ # t is not a tuple
+ return __builtin__.isinstance(obj, _builtin_type_map.get(t, t))
+ else:
+ # t is a tuple
+ for typ in t:
+ if __builtin__.isinstance(obj, _builtin_type_map.get(typ, typ)):
+ return True
+ return False
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Util/py3compat.py b/lib/Python/Lib/Crypto/Util/py3compat.py
new file mode 100644
index 000000000..34e5224f3
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/py3compat.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+#
+# Util/py3compat.py : Compatibility code for handling Py3k / Python 2.x
+#
+# Written in 2010 by Thorsten Behrens
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Compatibility code for handling string/bytes changes from Python 2.x to Py3k
+
+In Python 2.x, strings (of type ''str'') contain binary data, including encoded
+Unicode text (e.g. UTF-8). The separate type ''unicode'' holds Unicode text.
+Unicode literals are specified via the u'...' prefix. Indexing or slicing
+either type always produces a string of the same type as the original.
+Data read from a file is always of '''str'' type.
+
+In Python 3.x, strings (type ''str'') may only contain Unicode text. The u'...'
+prefix and the ''unicode'' type are now redundant. A new type (called
+''bytes'') has to be used for binary data (including any particular
+''encoding'' of a string). The b'...' prefix allows one to specify a binary
+literal. Indexing or slicing a string produces another string. Slicing a byte
+string produces another byte string, but the indexing operation produces an
+integer. Data read from a file is of '''str'' type if the file was opened in
+text mode, or of ''bytes'' type otherwise.
+
+Since PyCrypto aims at supporting both Python 2.x and 3.x, the following helper
+functions are used to keep the rest of the library as independent as possible
+from the actual Python version.
+
+In general, the code should always deal with binary strings, and use integers
+instead of 1-byte character strings.
+
+b(s)
+ Take a text string literal (with no prefix or with u'...' prefix) and
+ make a byte string.
+bchr(c)
+ Take an integer and make a 1-character byte string.
+bord(c)
+ Take the result of indexing on a byte string and make an integer.
+tobytes(s)
+ Take a text string, a byte string, or a sequence of character taken from
+ a byte string and make a byte string.
+"""
+
+__revision__ = "$Id$"
+
+import sys
+
+if sys.version_info[0] == 2:
+ def b(s):
+ return s
+ def bchr(s):
+ return chr(s)
+ def bstr(s):
+ return str(s)
+ def bord(s):
+ return ord(s)
+ if sys.version_info[1] == 1:
+ def tobytes(s):
+ try:
+ return s.encode('latin-1')
+ except:
+ return ''.join(s)
+ else:
+ def tobytes(s):
+ if isinstance(s, unicode):
+ return s.encode("latin-1")
+ else:
+ return ''.join(s)
+else:
+ def b(s):
+ return s.encode("latin-1") # utf-8 would cause some side-effects we don't want
+ def bchr(s):
+ return bytes([s])
+ def bstr(s):
+ if isinstance(s,str):
+ return bytes(s,"latin-1")
+ else:
+ return bytes(s)
+ def bord(s):
+ return s
+ def tobytes(s):
+ if isinstance(s,bytes):
+ return s
+ else:
+ if isinstance(s,str):
+ return s.encode("latin-1")
+ else:
+ return bytes(s)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/Util/randpool.py b/lib/Python/Lib/Crypto/Util/randpool.py
new file mode 100644
index 000000000..8b5a0b75d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/randpool.py
@@ -0,0 +1,82 @@
+#
+# randpool.py : Cryptographically strong random number generation
+#
+# Part of the Python Cryptography Toolkit
+#
+# Written by Andrew M. Kuchling, Mark Moraes, and others
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+#
+
+__revision__ = "$Id$"
+
+from Crypto.pct_warnings import RandomPool_DeprecationWarning
+import Crypto.Random
+import warnings
+
+class RandomPool:
+ """Deprecated. Use Random.new() instead.
+
+ See http://www.pycrypto.org/randpool-broken
+ """
+ def __init__(self, numbytes = 160, cipher=None, hash=None, file=None):
+ warnings.warn("This application uses RandomPool, which is BROKEN in older releases. See http://www.pycrypto.org/randpool-broken",
+ RandomPool_DeprecationWarning)
+ self.__rng = Crypto.Random.new()
+ self.bytes = numbytes
+ self.bits = self.bytes * 8
+ self.entropy = self.bits
+
+ def get_bytes(self, N):
+ return self.__rng.read(N)
+
+ def _updateEntropyEstimate(self, nbits):
+ self.entropy += nbits
+ if self.entropy < 0:
+ self.entropy = 0
+ elif self.entropy > self.bits:
+ self.entropy = self.bits
+
+ def _randomize(self, N=0, devname="/dev/urandom"):
+ """Dummy _randomize() function"""
+ self.__rng.flush()
+
+ def randomize(self, N=0):
+ """Dummy randomize() function"""
+ self.__rng.flush()
+
+ def stir(self, s=''):
+ """Dummy stir() function"""
+ self.__rng.flush()
+
+ def stir_n(self, N=3):
+ """Dummy stir_n() function"""
+ self.__rng.flush()
+
+ def add_event(self, s=''):
+ """Dummy add_event() function"""
+ self.__rng.flush()
+
+ def getBytes(self, N):
+ """Dummy getBytes() function"""
+ return self.get_bytes(N)
+
+ def addEvent(self, event, s=""):
+ """Dummy addEvent() function"""
+ return self.add_event()
diff --git a/lib/Python/Lib/Crypto/Util/winrandom.py b/lib/Python/Lib/Crypto/Util/winrandom.py
new file mode 100644
index 000000000..0242815af
--- /dev/null
+++ b/lib/Python/Lib/Crypto/Util/winrandom.py
@@ -0,0 +1,28 @@
+#
+# Util/winrandom.py : Stub for Crypto.Random.OSRNG.winrandom
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+__revision__ = "$Id$"
+
+from Crypto.Random.OSRNG.winrandom import *
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/lib/Python/Lib/Crypto/__init__.py b/lib/Python/Lib/Crypto/__init__.py
new file mode 100644
index 000000000..c27402e3a
--- /dev/null
+++ b/lib/Python/Lib/Crypto/__init__.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+"""Python Cryptography Toolkit
+
+A collection of cryptographic modules implementing various algorithms
+and protocols.
+
+Subpackages:
+
+Crypto.Cipher
+ Secret-key (AES, DES, ARC4) and public-key encryption (RSA PKCS#1) algorithms
+Crypto.Hash
+ Hashing algorithms (MD5, SHA, HMAC)
+Crypto.Protocol
+ Cryptographic protocols (Chaffing, all-or-nothing transform, key derivation
+ functions). This package does not contain any network protocols.
+Crypto.PublicKey
+ Public-key encryption and signature algorithms (RSA, DSA)
+Crypto.Signature
+ Public-key signature algorithms (RSA PKCS#1)
+Crypto.Util
+ Various useful modules and functions (long-to-string conversion, random number
+ generation, number theoretic functions)
+"""
+
+__all__ = ['Cipher', 'Hash', 'Protocol', 'PublicKey', 'Util', 'Signature']
+
+__version__ = '2.6.1' # See also below and setup.py
+__revision__ = "$Id$"
+
+# New software should look at this instead of at __version__ above.
+version_info = (2, 6, 1, 'final', 0) # See also above and setup.py
+
diff --git a/lib/Python/Lib/Crypto/pct_warnings.py b/lib/Python/Lib/Crypto/pct_warnings.py
new file mode 100644
index 000000000..9b4361e4d
--- /dev/null
+++ b/lib/Python/Lib/Crypto/pct_warnings.py
@@ -0,0 +1,60 @@
+# -*- coding: ascii -*-
+#
+# pct_warnings.py : PyCrypto warnings file
+#
+# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ===================================================================
+
+#
+# Base classes. All our warnings inherit from one of these in order to allow
+# the user to specifically filter them.
+#
+
+class CryptoWarning(Warning):
+ """Base class for PyCrypto warnings"""
+
+class CryptoDeprecationWarning(DeprecationWarning, CryptoWarning):
+ """Base PyCrypto DeprecationWarning class"""
+
+class CryptoRuntimeWarning(RuntimeWarning, CryptoWarning):
+ """Base PyCrypto RuntimeWarning class"""
+
+#
+# Warnings that we might actually use
+#
+
+class RandomPool_DeprecationWarning(CryptoDeprecationWarning):
+ """Issued when Crypto.Util.randpool.RandomPool is instantiated."""
+
+class ClockRewindWarning(CryptoRuntimeWarning):
+ """Warning for when the system clock moves backwards."""
+
+class GetRandomNumber_DeprecationWarning(CryptoDeprecationWarning):
+ """Issued when Crypto.Util.number.getRandomNumber is invoked."""
+
+class PowmInsecureWarning(CryptoRuntimeWarning):
+ """Warning for when _fastmath is built without mpz_powm_sec"""
+
+# By default, we want this warning to be shown every time we compensate for
+# clock rewinding.
+import warnings as _warnings
+_warnings.filterwarnings('always', category=ClockRewindWarning, append=1)
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/module/lib/Getch.py b/lib/Python/Lib/Getch.py
index 22b7ea7f8..22b7ea7f8 100644
--- a/module/lib/Getch.py
+++ b/lib/Python/Lib/Getch.py
diff --git a/module/lib/MultipartPostHandler.py b/lib/Python/Lib/MultipartPostHandler.py
index 94aee0193..94aee0193 100644
--- a/module/lib/MultipartPostHandler.py
+++ b/lib/Python/Lib/MultipartPostHandler.py
diff --git a/lib/Python/Lib/OpenSSL/SSL.py b/lib/Python/Lib/OpenSSL/SSL.py
new file mode 100644
index 000000000..d0cc93308
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/SSL.py
@@ -0,0 +1,1948 @@
+from sys import platform
+from functools import wraps, partial
+from itertools import count, chain
+from weakref import WeakValueDictionary
+from errno import errorcode
+
+from six import text_type as _text_type
+from six import binary_type as _binary_type
+from six import integer_types as integer_types
+from six import int2byte, indexbytes
+
+from OpenSSL._util import (
+ ffi as _ffi,
+ lib as _lib,
+ exception_from_error_queue as _exception_from_error_queue,
+ native as _native,
+ text_to_bytes_and_warn as _text_to_bytes_and_warn,
+ path_string as _path_string,
+ UNSPECIFIED as _UNSPECIFIED,
+)
+
+from OpenSSL.crypto import (
+ FILETYPE_PEM, _PassphraseHelper, PKey, X509Name, X509, X509Store)
+
+try:
+ _memoryview = memoryview
+except NameError:
+ class _memoryview(object):
+ pass
+
+try:
+ _buffer = buffer
+except NameError:
+ class _buffer(object):
+ pass
+
+OPENSSL_VERSION_NUMBER = _lib.OPENSSL_VERSION_NUMBER
+SSLEAY_VERSION = _lib.SSLEAY_VERSION
+SSLEAY_CFLAGS = _lib.SSLEAY_CFLAGS
+SSLEAY_PLATFORM = _lib.SSLEAY_PLATFORM
+SSLEAY_DIR = _lib.SSLEAY_DIR
+SSLEAY_BUILT_ON = _lib.SSLEAY_BUILT_ON
+
+SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN
+RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN
+
+SSLv2_METHOD = 1
+SSLv3_METHOD = 2
+SSLv23_METHOD = 3
+TLSv1_METHOD = 4
+TLSv1_1_METHOD = 5
+TLSv1_2_METHOD = 6
+
+OP_NO_SSLv2 = _lib.SSL_OP_NO_SSLv2
+OP_NO_SSLv3 = _lib.SSL_OP_NO_SSLv3
+OP_NO_TLSv1 = _lib.SSL_OP_NO_TLSv1
+
+OP_NO_TLSv1_1 = getattr(_lib, "SSL_OP_NO_TLSv1_1", 0)
+OP_NO_TLSv1_2 = getattr(_lib, "SSL_OP_NO_TLSv1_2", 0)
+
+try:
+ MODE_RELEASE_BUFFERS = _lib.SSL_MODE_RELEASE_BUFFERS
+except AttributeError:
+ pass
+
+OP_SINGLE_DH_USE = _lib.SSL_OP_SINGLE_DH_USE
+OP_EPHEMERAL_RSA = _lib.SSL_OP_EPHEMERAL_RSA
+OP_MICROSOFT_SESS_ID_BUG = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG
+OP_NETSCAPE_CHALLENGE_BUG = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG
+OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = _lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+OP_SSLREF2_REUSE_CERT_TYPE_BUG = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+OP_MICROSOFT_BIG_SSLV3_BUFFER = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+try:
+ OP_MSIE_SSLV2_RSA_PADDING = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING
+except AttributeError:
+ pass
+OP_SSLEAY_080_CLIENT_DH_BUG = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+OP_TLS_D5_BUG = _lib.SSL_OP_TLS_D5_BUG
+OP_TLS_BLOCK_PADDING_BUG = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG
+OP_DONT_INSERT_EMPTY_FRAGMENTS = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+OP_CIPHER_SERVER_PREFERENCE = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE
+OP_TLS_ROLLBACK_BUG = _lib.SSL_OP_TLS_ROLLBACK_BUG
+OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1
+OP_PKCS1_CHECK_2 = _lib.SSL_OP_PKCS1_CHECK_2
+OP_NETSCAPE_CA_DN_BUG = _lib.SSL_OP_NETSCAPE_CA_DN_BUG
+OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG= _lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
+try:
+ OP_NO_COMPRESSION = _lib.SSL_OP_NO_COMPRESSION
+except AttributeError:
+ pass
+
+OP_NO_QUERY_MTU = _lib.SSL_OP_NO_QUERY_MTU
+OP_COOKIE_EXCHANGE = _lib.SSL_OP_COOKIE_EXCHANGE
+try:
+ OP_NO_TICKET = _lib.SSL_OP_NO_TICKET
+except AttributeError:
+ pass
+
+OP_ALL = _lib.SSL_OP_ALL
+
+VERIFY_PEER = _lib.SSL_VERIFY_PEER
+VERIFY_FAIL_IF_NO_PEER_CERT = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT
+VERIFY_CLIENT_ONCE = _lib.SSL_VERIFY_CLIENT_ONCE
+VERIFY_NONE = _lib.SSL_VERIFY_NONE
+
+SESS_CACHE_OFF = _lib.SSL_SESS_CACHE_OFF
+SESS_CACHE_CLIENT = _lib.SSL_SESS_CACHE_CLIENT
+SESS_CACHE_SERVER = _lib.SSL_SESS_CACHE_SERVER
+SESS_CACHE_BOTH = _lib.SSL_SESS_CACHE_BOTH
+SESS_CACHE_NO_AUTO_CLEAR = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR
+SESS_CACHE_NO_INTERNAL_LOOKUP = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP
+SESS_CACHE_NO_INTERNAL_STORE = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE
+SESS_CACHE_NO_INTERNAL = _lib.SSL_SESS_CACHE_NO_INTERNAL
+
+SSL_ST_CONNECT = _lib.SSL_ST_CONNECT
+SSL_ST_ACCEPT = _lib.SSL_ST_ACCEPT
+SSL_ST_MASK = _lib.SSL_ST_MASK
+SSL_ST_INIT = _lib.SSL_ST_INIT
+SSL_ST_BEFORE = _lib.SSL_ST_BEFORE
+SSL_ST_OK = _lib.SSL_ST_OK
+SSL_ST_RENEGOTIATE = _lib.SSL_ST_RENEGOTIATE
+
+SSL_CB_LOOP = _lib.SSL_CB_LOOP
+SSL_CB_EXIT = _lib.SSL_CB_EXIT
+SSL_CB_READ = _lib.SSL_CB_READ
+SSL_CB_WRITE = _lib.SSL_CB_WRITE
+SSL_CB_ALERT = _lib.SSL_CB_ALERT
+SSL_CB_READ_ALERT = _lib.SSL_CB_READ_ALERT
+SSL_CB_WRITE_ALERT = _lib.SSL_CB_WRITE_ALERT
+SSL_CB_ACCEPT_LOOP = _lib.SSL_CB_ACCEPT_LOOP
+SSL_CB_ACCEPT_EXIT = _lib.SSL_CB_ACCEPT_EXIT
+SSL_CB_CONNECT_LOOP = _lib.SSL_CB_CONNECT_LOOP
+SSL_CB_CONNECT_EXIT = _lib.SSL_CB_CONNECT_EXIT
+SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START
+SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE
+
+class Error(Exception):
+ """
+ An error occurred in an `OpenSSL.SSL` API.
+ """
+
+
+
+_raise_current_error = partial(_exception_from_error_queue, Error)
+
+
+class WantReadError(Error):
+ pass
+
+
+
+class WantWriteError(Error):
+ pass
+
+
+
+class WantX509LookupError(Error):
+ pass
+
+
+
+class ZeroReturnError(Error):
+ pass
+
+
+
+class SysCallError(Error):
+ pass
+
+
+class _CallbackExceptionHelper(object):
+ """
+ A base class for wrapper classes that allow for intelligent exception
+ handling in OpenSSL callbacks.
+
+ :ivar list _problems: Any exceptions that occurred while executing in a
+ context where they could not be raised in the normal way. Typically
+ this is because OpenSSL has called into some Python code and requires a
+ return value. The exceptions are saved to be raised later when it is
+ possible to do so.
+ """
+ def __init__(self):
+ self._problems = []
+
+
+ def raise_if_problem(self):
+ """
+ Raise an exception from the OpenSSL error queue or that was previously
+ captured whe running a callback.
+ """
+ if self._problems:
+ try:
+ _raise_current_error()
+ except Error:
+ pass
+ raise self._problems.pop(0)
+
+
+class _VerifyHelper(_CallbackExceptionHelper):
+ """
+ Wrap a callback such that it can be used as a certificate verification
+ callback.
+ """
+ def __init__(self, callback):
+ _CallbackExceptionHelper.__init__(self)
+
+ @wraps(callback)
+ def wrapper(ok, store_ctx):
+ cert = X509.__new__(X509)
+ cert._x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx)
+ error_number = _lib.X509_STORE_CTX_get_error(store_ctx)
+ error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx)
+
+ index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx()
+ ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index)
+ connection = Connection._reverse_mapping[ssl]
+
+ try:
+ result = callback(connection, cert, error_number, error_depth, ok)
+ except Exception as e:
+ self._problems.append(e)
+ return 0
+ else:
+ if result:
+ _lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK)
+ return 1
+ else:
+ return 0
+
+ self.callback = _ffi.callback(
+ "int (*)(int, X509_STORE_CTX *)", wrapper)
+
+
+class _NpnAdvertiseHelper(_CallbackExceptionHelper):
+ """
+ Wrap a callback such that it can be used as an NPN advertisement callback.
+ """
+ def __init__(self, callback):
+ _CallbackExceptionHelper.__init__(self)
+
+ @wraps(callback)
+ def wrapper(ssl, out, outlen, arg):
+ try:
+ conn = Connection._reverse_mapping[ssl]
+ protos = callback(conn)
+
+ # Join the protocols into a Python bytestring, length-prefixing
+ # each element.
+ protostr = b''.join(
+ chain.from_iterable((int2byte(len(p)), p) for p in protos)
+ )
+
+ # Save our callback arguments on the connection object. This is
+ # done to make sure that they don't get freed before OpenSSL
+ # uses them. Then, return them appropriately in the output
+ # parameters.
+ conn._npn_advertise_callback_args = [
+ _ffi.new("unsigned int *", len(protostr)),
+ _ffi.new("unsigned char[]", protostr),
+ ]
+ outlen[0] = conn._npn_advertise_callback_args[0][0]
+ out[0] = conn._npn_advertise_callback_args[1]
+ return 0
+ except Exception as e:
+ self._problems.append(e)
+ return 2 # SSL_TLSEXT_ERR_ALERT_FATAL
+
+ self.callback = _ffi.callback(
+ "int (*)(SSL *, const unsigned char **, unsigned int *, void *)",
+ wrapper
+ )
+
+
+class _NpnSelectHelper(_CallbackExceptionHelper):
+ """
+ Wrap a callback such that it can be used as an NPN selection callback.
+ """
+ def __init__(self, callback):
+ _CallbackExceptionHelper.__init__(self)
+
+ @wraps(callback)
+ def wrapper(ssl, out, outlen, in_, inlen, arg):
+ try:
+ conn = Connection._reverse_mapping[ssl]
+
+ # The string passed to us is actually made up of multiple
+ # length-prefixed bytestrings. We need to split that into a
+ # list.
+ instr = _ffi.buffer(in_, inlen)[:]
+ protolist = []
+ while instr:
+ l = indexbytes(instr, 0)
+ proto = instr[1:l+1]
+ protolist.append(proto)
+ instr = instr[l+1:]
+
+ # Call the callback
+ outstr = callback(conn, protolist)
+
+ # Save our callback arguments on the connection object. This is
+ # done to make sure that they don't get freed before OpenSSL
+ # uses them. Then, return them appropriately in the output
+ # parameters.
+ conn._npn_select_callback_args = [
+ _ffi.new("unsigned char *", len(outstr)),
+ _ffi.new("unsigned char[]", outstr),
+ ]
+ outlen[0] = conn._npn_select_callback_args[0][0]
+ out[0] = conn._npn_select_callback_args[1]
+ return 0
+ except Exception as e:
+ self._problems.append(e)
+ return 2 # SSL_TLSEXT_ERR_ALERT_FATAL
+
+ self.callback = _ffi.callback(
+ "int (*)(SSL *, unsigned char **, unsigned char *, "
+ "const unsigned char *, unsigned int, void *)",
+ wrapper
+ )
+
+
+class _ALPNSelectHelper(_CallbackExceptionHelper):
+ """
+ Wrap a callback such that it can be used as an ALPN selection callback.
+ """
+ def __init__(self, callback):
+ _CallbackExceptionHelper.__init__(self)
+
+ @wraps(callback)
+ def wrapper(ssl, out, outlen, in_, inlen, arg):
+ try:
+ conn = Connection._reverse_mapping[ssl]
+
+ # The string passed to us is made up of multiple
+ # length-prefixed bytestrings. We need to split that into a
+ # list.
+ instr = _ffi.buffer(in_, inlen)[:]
+ protolist = []
+ while instr:
+ encoded_len = indexbytes(instr, 0)
+ proto = instr[1:encoded_len + 1]
+ protolist.append(proto)
+ instr = instr[encoded_len + 1:]
+
+ # Call the callback
+ outstr = callback(conn, protolist)
+
+ if not isinstance(outstr, _binary_type):
+ raise TypeError("ALPN callback must return a bytestring.")
+
+ # Save our callback arguments on the connection object to make
+ # sure that they don't get freed before OpenSSL can use them.
+ # Then, return them in the appropriate output parameters.
+ conn._alpn_select_callback_args = [
+ _ffi.new("unsigned char *", len(outstr)),
+ _ffi.new("unsigned char[]", outstr),
+ ]
+ outlen[0] = conn._alpn_select_callback_args[0][0]
+ out[0] = conn._alpn_select_callback_args[1]
+ return 0
+ except Exception as e:
+ self._problems.append(e)
+ return 2 # SSL_TLSEXT_ERR_ALERT_FATAL
+
+ self.callback = _ffi.callback(
+ "int (*)(SSL *, unsigned char **, unsigned char *, "
+ "const unsigned char *, unsigned int, void *)",
+ wrapper
+ )
+
+
+def _asFileDescriptor(obj):
+ fd = None
+ if not isinstance(obj, integer_types):
+ meth = getattr(obj, "fileno", None)
+ if meth is not None:
+ obj = meth()
+
+ if isinstance(obj, integer_types):
+ fd = obj
+
+ if not isinstance(fd, integer_types):
+ raise TypeError("argument must be an int, or have a fileno() method.")
+ elif fd < 0:
+ raise ValueError(
+ "file descriptor cannot be a negative integer (%i)" % (fd,))
+
+ return fd
+
+
+
+def SSLeay_version(type):
+ """
+ Return a string describing the version of OpenSSL in use.
+
+ :param type: One of the SSLEAY_ constants defined in this module.
+ """
+ return _ffi.string(_lib.SSLeay_version(type))
+
+
+def _requires_npn(func):
+ """
+ Wraps any function that requires NPN support in OpenSSL, ensuring that
+ NotImplementedError is raised if NPN is not present.
+ """
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if not _lib.Cryptography_HAS_NEXTPROTONEG:
+ raise NotImplementedError("NPN not available.")
+
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+
+def _requires_alpn(func):
+ """
+ Wraps any function that requires ALPN support in OpenSSL, ensuring that
+ NotImplementedError is raised if ALPN support is not present.
+ """
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if not _lib.Cryptography_HAS_ALPN:
+ raise NotImplementedError("ALPN not available.")
+
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+
+class Session(object):
+ pass
+
+
+
+class Context(object):
+ """
+ :py:obj:`OpenSSL.SSL.Context` instances define the parameters for setting up
+ new SSL connections.
+ """
+ _methods = {
+ SSLv2_METHOD: "SSLv2_method",
+ SSLv3_METHOD: "SSLv3_method",
+ SSLv23_METHOD: "SSLv23_method",
+ TLSv1_METHOD: "TLSv1_method",
+ TLSv1_1_METHOD: "TLSv1_1_method",
+ TLSv1_2_METHOD: "TLSv1_2_method",
+ }
+ _methods = dict(
+ (identifier, getattr(_lib, name))
+ for (identifier, name) in _methods.items()
+ if getattr(_lib, name, None) is not None)
+
+
+ def __init__(self, method):
+ """
+ :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or
+ TLSv1_METHOD.
+ """
+ if not isinstance(method, integer_types):
+ raise TypeError("method must be an integer")
+
+ try:
+ method_func = self._methods[method]
+ except KeyError:
+ raise ValueError("No such protocol")
+
+ method_obj = method_func()
+ if method_obj == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ context = _lib.SSL_CTX_new(method_obj)
+ if context == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ context = _ffi.gc(context, _lib.SSL_CTX_free)
+
+ self._context = context
+ self._passphrase_helper = None
+ self._passphrase_callback = None
+ self._passphrase_userdata = None
+ self._verify_helper = None
+ self._verify_callback = None
+ self._info_callback = None
+ self._tlsext_servername_callback = None
+ self._app_data = None
+ self._npn_advertise_helper = None
+ self._npn_advertise_callback = None
+ self._npn_select_helper = None
+ self._npn_select_callback = None
+ self._alpn_select_helper = None
+ self._alpn_select_callback = None
+
+ # SSL_CTX_set_app_data(self->ctx, self);
+ # SSL_CTX_set_mode(self->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE |
+ # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
+ # SSL_MODE_AUTO_RETRY);
+ self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE)
+
+
+ def load_verify_locations(self, cafile, capath=None):
+ """
+ Let SSL know where we can find trusted certificates for the certificate
+ chain
+
+ :param cafile: In which file we can find the certificates (``bytes`` or
+ ``unicode``).
+ :param capath: In which directory we can find the certificates
+ (``bytes`` or ``unicode``).
+
+ :return: None
+ """
+ if cafile is None:
+ cafile = _ffi.NULL
+ else:
+ cafile = _path_string(cafile)
+
+ if capath is None:
+ capath = _ffi.NULL
+ else:
+ capath = _path_string(capath)
+
+ load_result = _lib.SSL_CTX_load_verify_locations(self._context, cafile, capath)
+ if not load_result:
+ _raise_current_error()
+
+
+ def _wrap_callback(self, callback):
+ @wraps(callback)
+ def wrapper(size, verify, userdata):
+ return callback(size, verify, self._passphrase_userdata)
+ return _PassphraseHelper(
+ FILETYPE_PEM, wrapper, more_args=True, truncate=True)
+
+
+ def set_passwd_cb(self, callback, userdata=None):
+ """
+ Set the passphrase callback
+
+ :param callback: The Python callback to use
+ :param userdata: (optional) A Python object which will be given as
+ argument to the callback
+ :return: None
+ """
+ if not callable(callback):
+ raise TypeError("callback must be callable")
+
+ self._passphrase_helper = self._wrap_callback(callback)
+ self._passphrase_callback = self._passphrase_helper.callback
+ _lib.SSL_CTX_set_default_passwd_cb(
+ self._context, self._passphrase_callback)
+ self._passphrase_userdata = userdata
+
+
+ def set_default_verify_paths(self):
+ """
+ Use the platform-specific CA certificate locations
+
+ :return: None
+ """
+ set_result = _lib.SSL_CTX_set_default_verify_paths(self._context)
+ if not set_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def use_certificate_chain_file(self, certfile):
+ """
+ Load a certificate chain from a file
+
+ :param certfile: The name of the certificate chain file (``bytes`` or
+ ``unicode``).
+
+ :return: None
+ """
+ certfile = _path_string(certfile)
+
+ result = _lib.SSL_CTX_use_certificate_chain_file(self._context, certfile)
+ if not result:
+ _raise_current_error()
+
+
+ def use_certificate_file(self, certfile, filetype=FILETYPE_PEM):
+ """
+ Load a certificate from a file
+
+ :param certfile: The name of the certificate file (``bytes`` or
+ ``unicode``).
+ :param filetype: (optional) The encoding of the file, default is PEM
+
+ :return: None
+ """
+ certfile = _path_string(certfile)
+ if not isinstance(filetype, integer_types):
+ raise TypeError("filetype must be an integer")
+
+ use_result = _lib.SSL_CTX_use_certificate_file(self._context, certfile, filetype)
+ if not use_result:
+ _raise_current_error()
+
+
+ def use_certificate(self, cert):
+ """
+ Load a certificate from a X509 object
+
+ :param cert: The X509 object
+ :return: None
+ """
+ if not isinstance(cert, X509):
+ raise TypeError("cert must be an X509 instance")
+
+ use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509)
+ if not use_result:
+ _raise_current_error()
+
+
+ def add_extra_chain_cert(self, certobj):
+ """
+ Add certificate to chain
+
+ :param certobj: The X509 certificate object to add to the chain
+ :return: None
+ """
+ if not isinstance(certobj, X509):
+ raise TypeError("certobj must be an X509 instance")
+
+ copy = _lib.X509_dup(certobj._x509)
+ add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy)
+ if not add_result:
+ # TODO: This is untested.
+ _lib.X509_free(copy)
+ _raise_current_error()
+
+
+ def _raise_passphrase_exception(self):
+ if self._passphrase_helper is None:
+ _raise_current_error()
+ exception = self._passphrase_helper.raise_if_problem(Error)
+ if exception is not None:
+ raise exception
+
+
+ def use_privatekey_file(self, keyfile, filetype=_UNSPECIFIED):
+ """
+ Load a private key from a file
+
+ :param keyfile: The name of the key file (``bytes`` or ``unicode``)
+ :param filetype: (optional) The encoding of the file, default is PEM
+
+ :return: None
+ """
+ keyfile = _path_string(keyfile)
+
+ if filetype is _UNSPECIFIED:
+ filetype = FILETYPE_PEM
+ elif not isinstance(filetype, integer_types):
+ raise TypeError("filetype must be an integer")
+
+ use_result = _lib.SSL_CTX_use_PrivateKey_file(
+ self._context, keyfile, filetype)
+ if not use_result:
+ self._raise_passphrase_exception()
+
+
+ def use_privatekey(self, pkey):
+ """
+ Load a private key from a PKey object
+
+ :param pkey: The PKey object
+ :return: None
+ """
+ if not isinstance(pkey, PKey):
+ raise TypeError("pkey must be a PKey instance")
+
+ use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey)
+ if not use_result:
+ self._raise_passphrase_exception()
+
+
+ def check_privatekey(self):
+ """
+ Check that the private key and certificate match up
+
+ :return: None (raises an exception if something's wrong)
+ """
+ if not _lib.SSL_CTX_check_private_key(self._context):
+ _raise_current_error()
+
+
+ def load_client_ca(self, cafile):
+ """
+ Load the trusted certificates that will be sent to the client (basically
+ telling the client "These are the guys I trust"). Does not actually
+ imply any of the certificates are trusted; that must be configured
+ separately.
+
+ :param cafile: The name of the certificates file
+ :return: None
+ """
+
+ def set_session_id(self, buf):
+ """
+ Set the session identifier. This is needed if you want to do session
+ resumption.
+
+ :param buf: A Python object that can be safely converted to a string
+ :returns: None
+ """
+
+ def set_session_cache_mode(self, mode):
+ """
+ Enable/disable session caching and specify the mode used.
+
+ :param mode: One or more of the SESS_CACHE_* flags (combine using
+ bitwise or)
+ :returns: The previously set caching mode.
+ """
+ if not isinstance(mode, integer_types):
+ raise TypeError("mode must be an integer")
+
+ return _lib.SSL_CTX_set_session_cache_mode(self._context, mode)
+
+
+ def get_session_cache_mode(self):
+ """
+ :returns: The currently used cache mode.
+ """
+ return _lib.SSL_CTX_get_session_cache_mode(self._context)
+
+
+ def set_verify(self, mode, callback):
+ """
+ Set the verify mode and verify callback
+
+ :param mode: The verify mode, this is either VERIFY_NONE or
+ VERIFY_PEER combined with possible other flags
+ :param callback: The Python callback to use
+ :return: None
+
+ See SSL_CTX_set_verify(3SSL) for further details.
+ """
+ if not isinstance(mode, integer_types):
+ raise TypeError("mode must be an integer")
+
+ if not callable(callback):
+ raise TypeError("callback must be callable")
+
+ self._verify_helper = _VerifyHelper(callback)
+ self._verify_callback = self._verify_helper.callback
+ _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback)
+
+
+ def set_verify_depth(self, depth):
+ """
+ Set the verify depth
+
+ :param depth: An integer specifying the verify depth
+ :return: None
+ """
+ if not isinstance(depth, integer_types):
+ raise TypeError("depth must be an integer")
+
+ _lib.SSL_CTX_set_verify_depth(self._context, depth)
+
+
+ def get_verify_mode(self):
+ """
+ Get the verify mode
+
+ :return: The verify mode
+ """
+ return _lib.SSL_CTX_get_verify_mode(self._context)
+
+
+ def get_verify_depth(self):
+ """
+ Get the verify depth
+
+ :return: The verify depth
+ """
+ return _lib.SSL_CTX_get_verify_depth(self._context)
+
+
+ def load_tmp_dh(self, dhfile):
+ """
+ Load parameters for Ephemeral Diffie-Hellman
+
+ :param dhfile: The file to load EDH parameters from (``bytes`` or
+ ``unicode``).
+
+ :return: None
+ """
+ dhfile = _path_string(dhfile)
+
+ bio = _lib.BIO_new_file(dhfile, b"r")
+ if bio == _ffi.NULL:
+ _raise_current_error()
+ bio = _ffi.gc(bio, _lib.BIO_free)
+
+ dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL)
+ dh = _ffi.gc(dh, _lib.DH_free)
+ _lib.SSL_CTX_set_tmp_dh(self._context, dh)
+
+
+ def set_tmp_ecdh(self, curve):
+ """
+ Select a curve to use for ECDHE key exchange.
+
+ :param curve: A curve object to use as returned by either
+ :py:meth:`OpenSSL.crypto.get_elliptic_curve` or
+ :py:meth:`OpenSSL.crypto.get_elliptic_curves`.
+
+ :return: None
+ """
+ _lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY())
+
+
+ def set_cipher_list(self, cipher_list):
+ """
+ Change the cipher list
+
+ :param cipher_list: A cipher list, see ciphers(1)
+ :return: None
+ """
+ if isinstance(cipher_list, _text_type):
+ cipher_list = cipher_list.encode("ascii")
+
+ if not isinstance(cipher_list, bytes):
+ raise TypeError("cipher_list must be bytes or unicode")
+
+ result = _lib.SSL_CTX_set_cipher_list(self._context, cipher_list)
+ if not result:
+ _raise_current_error()
+
+
+ def set_client_ca_list(self, certificate_authorities):
+ """
+ Set the list of preferred client certificate signers for this server context.
+
+ This list of certificate authorities will be sent to the client when the
+ server requests a client certificate.
+
+ :param certificate_authorities: a sequence of X509Names.
+ :return: None
+ """
+ name_stack = _lib.sk_X509_NAME_new_null()
+ if name_stack == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ try:
+ for ca_name in certificate_authorities:
+ if not isinstance(ca_name, X509Name):
+ raise TypeError(
+ "client CAs must be X509Name objects, not %s objects" % (
+ type(ca_name).__name__,))
+ copy = _lib.X509_NAME_dup(ca_name._name)
+ if copy == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ push_result = _lib.sk_X509_NAME_push(name_stack, copy)
+ if not push_result:
+ _lib.X509_NAME_free(copy)
+ _raise_current_error()
+ except:
+ _lib.sk_X509_NAME_free(name_stack)
+ raise
+
+ _lib.SSL_CTX_set_client_CA_list(self._context, name_stack)
+
+
+ def add_client_ca(self, certificate_authority):
+ """
+ Add the CA certificate to the list of preferred signers for this context.
+
+ The list of certificate authorities will be sent to the client when the
+ server requests a client certificate.
+
+ :param certificate_authority: certificate authority's X509 certificate.
+ :return: None
+ """
+ if not isinstance(certificate_authority, X509):
+ raise TypeError("certificate_authority must be an X509 instance")
+
+ add_result = _lib.SSL_CTX_add_client_CA(
+ self._context, certificate_authority._x509)
+ if not add_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def set_timeout(self, timeout):
+ """
+ Set session timeout
+
+ :param timeout: The timeout in seconds
+ :return: The previous session timeout
+ """
+ if not isinstance(timeout, integer_types):
+ raise TypeError("timeout must be an integer")
+
+ return _lib.SSL_CTX_set_timeout(self._context, timeout)
+
+
+ def get_timeout(self):
+ """
+ Get the session timeout
+
+ :return: The session timeout
+ """
+ return _lib.SSL_CTX_get_timeout(self._context)
+
+
+ def set_info_callback(self, callback):
+ """
+ Set the info callback
+
+ :param callback: The Python callback to use
+ :return: None
+ """
+ @wraps(callback)
+ def wrapper(ssl, where, return_code):
+ callback(Connection._reverse_mapping[ssl], where, return_code)
+ self._info_callback = _ffi.callback(
+ "void (*)(const SSL *, int, int)", wrapper)
+ _lib.SSL_CTX_set_info_callback(self._context, self._info_callback)
+
+
+ def get_app_data(self):
+ """
+ Get the application data (supplied via set_app_data())
+
+ :return: The application data
+ """
+ return self._app_data
+
+
+ def set_app_data(self, data):
+ """
+ Set the application data (will be returned from get_app_data())
+
+ :param data: Any Python object
+ :return: None
+ """
+ self._app_data = data
+
+
+ def get_cert_store(self):
+ """
+ Get the certificate store for the context.
+
+ :return: A X509Store object or None if it does not have one.
+ """
+ store = _lib.SSL_CTX_get_cert_store(self._context)
+ if store == _ffi.NULL:
+ # TODO: This is untested.
+ return None
+
+ pystore = X509Store.__new__(X509Store)
+ pystore._store = store
+ return pystore
+
+
+ def set_options(self, options):
+ """
+ Add options. Options set before are not cleared!
+
+ :param options: The options to add.
+ :return: The new option bitmask.
+ """
+ if not isinstance(options, integer_types):
+ raise TypeError("options must be an integer")
+
+ return _lib.SSL_CTX_set_options(self._context, options)
+
+
+ def set_mode(self, mode):
+ """
+ Add modes via bitmask. Modes set before are not cleared!
+
+ :param mode: The mode to add.
+ :return: The new mode bitmask.
+ """
+ if not isinstance(mode, integer_types):
+ raise TypeError("mode must be an integer")
+
+ return _lib.SSL_CTX_set_mode(self._context, mode)
+
+
+ def set_tlsext_servername_callback(self, callback):
+ """
+ Specify a callback function to be called when clients specify a server name.
+
+ :param callback: The callback function. It will be invoked with one
+ argument, the Connection instance.
+ """
+ @wraps(callback)
+ def wrapper(ssl, alert, arg):
+ callback(Connection._reverse_mapping[ssl])
+ return 0
+
+ self._tlsext_servername_callback = _ffi.callback(
+ "int (*)(const SSL *, int *, void *)", wrapper)
+ _lib.SSL_CTX_set_tlsext_servername_callback(
+ self._context, self._tlsext_servername_callback)
+
+
+ @_requires_npn
+ def set_npn_advertise_callback(self, callback):
+ """
+ Specify a callback function that will be called when offering `Next
+ Protocol Negotiation
+ <https://technotes.googlecode.com/git/nextprotoneg.html>`_ as a server.
+
+ :param callback: The callback function. It will be invoked with one
+ argument, the Connection instance. It should return a list of
+ bytestrings representing the advertised protocols, like
+ ``[b'http/1.1', b'spdy/2']``.
+ """
+ self._npn_advertise_helper = _NpnAdvertiseHelper(callback)
+ self._npn_advertise_callback = self._npn_advertise_helper.callback
+ _lib.SSL_CTX_set_next_protos_advertised_cb(
+ self._context, self._npn_advertise_callback, _ffi.NULL)
+
+
+ @_requires_npn
+ def set_npn_select_callback(self, callback):
+ """
+ Specify a callback function that will be called when a server offers
+ Next Protocol Negotiation options.
+
+ :param callback: The callback function. It will be invoked with two
+ arguments: the Connection, and a list of offered protocols as
+ bytestrings, e.g. ``[b'http/1.1', b'spdy/2']``. It should return
+ one of those bytestrings, the chosen protocol.
+ """
+ self._npn_select_helper = _NpnSelectHelper(callback)
+ self._npn_select_callback = self._npn_select_helper.callback
+ _lib.SSL_CTX_set_next_proto_select_cb(
+ self._context, self._npn_select_callback, _ffi.NULL)
+
+ @_requires_alpn
+ def set_alpn_protos(self, protos):
+ """
+ Specify the clients ALPN protocol list.
+
+ These protocols are offered to the server during protocol negotiation.
+
+ :param protos: A list of the protocols to be offered to the server.
+ This list should be a Python list of bytestrings representing the
+ protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``.
+ """
+ # Take the list of protocols and join them together, prefixing them
+ # with their lengths.
+ protostr = b''.join(
+ chain.from_iterable((int2byte(len(p)), p) for p in protos)
+ )
+
+ # Build a C string from the list. We don't need to save this off
+ # because OpenSSL immediately copies the data out.
+ input_str = _ffi.new("unsigned char[]", protostr)
+ input_str_len = _ffi.cast("unsigned", len(protostr))
+ _lib.SSL_CTX_set_alpn_protos(self._context, input_str, input_str_len)
+
+ @_requires_alpn
+ def set_alpn_select_callback(self, callback):
+ """
+ Set the callback to handle ALPN protocol choice.
+
+ :param callback: The callback function. It will be invoked with two
+ arguments: the Connection, and a list of offered protocols as
+ bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It should return
+ one of those bytestrings, the chosen protocol.
+ """
+ self._alpn_select_helper = _ALPNSelectHelper(callback)
+ self._alpn_select_callback = self._alpn_select_helper.callback
+ _lib.SSL_CTX_set_alpn_select_cb(
+ self._context, self._alpn_select_callback, _ffi.NULL)
+
+ContextType = Context
+
+
+
+class Connection(object):
+ """
+ """
+ _reverse_mapping = WeakValueDictionary()
+
+ def __init__(self, context, socket=None):
+ """
+ Create a new Connection object, using the given OpenSSL.SSL.Context
+ instance and socket.
+
+ :param context: An SSL Context to use for this connection
+ :param socket: The socket to use for transport layer
+ """
+ if not isinstance(context, Context):
+ raise TypeError("context must be a Context instance")
+
+ ssl = _lib.SSL_new(context._context)
+ self._ssl = _ffi.gc(ssl, _lib.SSL_free)
+ self._context = context
+
+ # References to strings used for Next Protocol Negotiation. OpenSSL's
+ # header files suggest that these might get copied at some point, but
+ # doesn't specify when, so we store them here to make sure they don't
+ # get freed before OpenSSL uses them.
+ self._npn_advertise_callback_args = None
+ self._npn_select_callback_args = None
+
+ # References to strings used for Application Layer Protocol
+ # Negotiation. These strings get copied at some point but it's well
+ # after the callback returns, so we have to hang them somewhere to
+ # avoid them getting freed.
+ self._alpn_select_callback_args = None
+
+ self._reverse_mapping[self._ssl] = self
+
+ if socket is None:
+ self._socket = None
+ # Don't set up any gc for these, SSL_free will take care of them.
+ self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem())
+ self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem())
+
+ if self._into_ssl == _ffi.NULL or self._from_ssl == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ _lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl)
+ else:
+ self._into_ssl = None
+ self._from_ssl = None
+ self._socket = socket
+ set_result = _lib.SSL_set_fd(self._ssl, _asFileDescriptor(self._socket))
+ if not set_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def __getattr__(self, name):
+ """
+ Look up attributes on the wrapped socket object if they are not found on
+ the Connection object.
+ """
+ return getattr(self._socket, name)
+
+
+ def _raise_ssl_error(self, ssl, result):
+ if self._context._verify_helper is not None:
+ self._context._verify_helper.raise_if_problem()
+ if self._context._npn_advertise_helper is not None:
+ self._context._npn_advertise_helper.raise_if_problem()
+ if self._context._npn_select_helper is not None:
+ self._context._npn_select_helper.raise_if_problem()
+ if self._context._alpn_select_helper is not None:
+ self._context._alpn_select_helper.raise_if_problem()
+
+ error = _lib.SSL_get_error(ssl, result)
+ if error == _lib.SSL_ERROR_WANT_READ:
+ raise WantReadError()
+ elif error == _lib.SSL_ERROR_WANT_WRITE:
+ raise WantWriteError()
+ elif error == _lib.SSL_ERROR_ZERO_RETURN:
+ raise ZeroReturnError()
+ elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP:
+ # TODO: This is untested.
+ raise WantX509LookupError()
+ elif error == _lib.SSL_ERROR_SYSCALL:
+ if _lib.ERR_peek_error() == 0:
+ if result < 0:
+ if platform == "win32":
+ errno = _ffi.getwinerror()[0]
+ else:
+ errno = _ffi.errno
+ raise SysCallError(errno, errorcode.get(errno))
+ else:
+ raise SysCallError(-1, "Unexpected EOF")
+ else:
+ # TODO: This is untested.
+ _raise_current_error()
+ elif error == _lib.SSL_ERROR_NONE:
+ pass
+ else:
+ _raise_current_error()
+
+
+ def get_context(self):
+ """
+ Get session context
+ """
+ return self._context
+
+
+ def set_context(self, context):
+ """
+ Switch this connection to a new session context
+
+ :param context: A :py:class:`Context` instance giving the new session
+ context to use.
+ """
+ if not isinstance(context, Context):
+ raise TypeError("context must be a Context instance")
+
+ _lib.SSL_set_SSL_CTX(self._ssl, context._context)
+ self._context = context
+
+
+ def get_servername(self):
+ """
+ Retrieve the servername extension value if provided in the client hello
+ message, or None if there wasn't one.
+
+ :return: A byte string giving the server name or :py:data:`None`.
+ """
+ name = _lib.SSL_get_servername(self._ssl, _lib.TLSEXT_NAMETYPE_host_name)
+ if name == _ffi.NULL:
+ return None
+
+ return _ffi.string(name)
+
+
+ def set_tlsext_host_name(self, name):
+ """
+ Set the value of the servername extension to send in the client hello.
+
+ :param name: A byte string giving the name.
+ """
+ if not isinstance(name, bytes):
+ raise TypeError("name must be a byte string")
+ elif b"\0" in name:
+ raise TypeError("name must not contain NUL byte")
+
+ # XXX I guess this can fail sometimes?
+ _lib.SSL_set_tlsext_host_name(self._ssl, name)
+
+
+ def pending(self):
+ """
+ Get the number of bytes that can be safely read from the connection
+
+ :return: The number of bytes available in the receive buffer.
+ """
+ return _lib.SSL_pending(self._ssl)
+
+
+ def send(self, buf, flags=0):
+ """
+ Send data on the connection. NOTE: If you get one of the WantRead,
+ WantWrite or WantX509Lookup exceptions on this, you have to call the
+ method again with the SAME buffer.
+
+ :param buf: The string, buffer or memoryview to send
+ :param flags: (optional) Included for compatibility with the socket
+ API, the value is ignored
+ :return: The number of bytes written
+ """
+ # Backward compatibility
+ buf = _text_to_bytes_and_warn("buf", buf)
+
+ if isinstance(buf, _memoryview):
+ buf = buf.tobytes()
+ if isinstance(buf, _buffer):
+ buf = str(buf)
+ if not isinstance(buf, bytes):
+ raise TypeError("data must be a memoryview, buffer or byte string")
+
+ result = _lib.SSL_write(self._ssl, buf, len(buf))
+ self._raise_ssl_error(self._ssl, result)
+ return result
+ write = send
+
+
+ def sendall(self, buf, flags=0):
+ """
+ Send "all" data on the connection. This calls send() repeatedly until
+ all data is sent. If an error occurs, it's impossible to tell how much
+ data has been sent.
+
+ :param buf: The string, buffer or memoryview to send
+ :param flags: (optional) Included for compatibility with the socket
+ API, the value is ignored
+ :return: The number of bytes written
+ """
+ buf = _text_to_bytes_and_warn("buf", buf)
+
+ if isinstance(buf, _memoryview):
+ buf = buf.tobytes()
+ if isinstance(buf, _buffer):
+ buf = str(buf)
+ if not isinstance(buf, bytes):
+ raise TypeError("buf must be a memoryview, buffer or byte string")
+
+ left_to_send = len(buf)
+ total_sent = 0
+ data = _ffi.new("char[]", buf)
+
+ while left_to_send:
+ result = _lib.SSL_write(self._ssl, data + total_sent, left_to_send)
+ self._raise_ssl_error(self._ssl, result)
+ total_sent += result
+ left_to_send -= result
+
+
+ def recv(self, bufsiz, flags=None):
+ """
+ Receive data on the connection. NOTE: If you get one of the WantRead,
+ WantWrite or WantX509Lookup exceptions on this, you have to call the
+ method again with the SAME buffer.
+
+ :param bufsiz: The maximum number of bytes to read
+ :param flags: (optional) Included for compatibility with the socket
+ API, the value is ignored
+ :return: The string read from the Connection
+ """
+ buf = _ffi.new("char[]", bufsiz)
+ result = _lib.SSL_read(self._ssl, buf, bufsiz)
+ self._raise_ssl_error(self._ssl, result)
+ return _ffi.buffer(buf, result)[:]
+ read = recv
+
+
+ def recv_into(self, buffer, nbytes=None, flags=None):
+ """
+ Receive data on the connection and store the data into a buffer rather
+ than creating a new string.
+
+ :param buffer: The buffer to copy into.
+ :param nbytes: (optional) The maximum number of bytes to read into the
+ buffer. If not present, defaults to the size of the buffer. If
+ larger than the size of the buffer, is reduced to the size of the
+ buffer.
+ :param flags: (optional) Included for compatibility with the socket
+ API, the value is ignored.
+ :return: The number of bytes read into the buffer.
+ """
+ if nbytes is None:
+ nbytes = len(buffer)
+ else:
+ nbytes = min(nbytes, len(buffer))
+
+ # We need to create a temporary buffer. This is annoying, it would be
+ # better if we could pass memoryviews straight into the SSL_read call,
+ # but right now we can't. Revisit this if CFFI gets that ability.
+ buf = _ffi.new("char[]", nbytes)
+ result = _lib.SSL_read(self._ssl, buf, nbytes)
+ self._raise_ssl_error(self._ssl, result)
+
+ # This strange line is all to avoid a memory copy. The buffer protocol
+ # should allow us to assign a CFFI buffer to the LHS of this line, but
+ # on CPython 3.3+ that segfaults. As a workaround, we can temporarily
+ # wrap it in a memoryview, except on Python 2.6 which doesn't have a
+ # memoryview type.
+ try:
+ buffer[:result] = memoryview(_ffi.buffer(buf, result))
+ except NameError:
+ buffer[:result] = _ffi.buffer(buf, result)
+
+ return result
+
+
+ def _handle_bio_errors(self, bio, result):
+ if _lib.BIO_should_retry(bio):
+ if _lib.BIO_should_read(bio):
+ raise WantReadError()
+ elif _lib.BIO_should_write(bio):
+ # TODO: This is untested.
+ raise WantWriteError()
+ elif _lib.BIO_should_io_special(bio):
+ # TODO: This is untested. I think io_special means the socket
+ # BIO has a not-yet connected socket.
+ raise ValueError("BIO_should_io_special")
+ else:
+ # TODO: This is untested.
+ raise ValueError("unknown bio failure")
+ else:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def bio_read(self, bufsiz):
+ """
+ When using non-socket connections this function reads the "dirty" data
+ that would have traveled away on the network.
+
+ :param bufsiz: The maximum number of bytes to read
+ :return: The string read.
+ """
+ if self._from_ssl is None:
+ raise TypeError("Connection sock was not None")
+
+ if not isinstance(bufsiz, integer_types):
+ raise TypeError("bufsiz must be an integer")
+
+ buf = _ffi.new("char[]", bufsiz)
+ result = _lib.BIO_read(self._from_ssl, buf, bufsiz)
+ if result <= 0:
+ self._handle_bio_errors(self._from_ssl, result)
+
+ return _ffi.buffer(buf, result)[:]
+
+
+ def bio_write(self, buf):
+ """
+ When using non-socket connections this function sends "dirty" data that
+ would have traveled in on the network.
+
+ :param buf: The string to put into the memory BIO.
+ :return: The number of bytes written
+ """
+ buf = _text_to_bytes_and_warn("buf", buf)
+
+ if self._into_ssl is None:
+ raise TypeError("Connection sock was not None")
+
+ if not isinstance(buf, bytes):
+ raise TypeError("buf must be a byte string")
+
+ result = _lib.BIO_write(self._into_ssl, buf, len(buf))
+ if result <= 0:
+ self._handle_bio_errors(self._into_ssl, result)
+ return result
+
+
+ def renegotiate(self):
+ """
+ Renegotiate the session
+
+ :return: True if the renegotiation can be started, false otherwise
+ """
+
+ def do_handshake(self):
+ """
+ Perform an SSL handshake (usually called after renegotiate() or one of
+ set_*_state()). This can raise the same exceptions as send and recv.
+
+ :return: None.
+ """
+ result = _lib.SSL_do_handshake(self._ssl)
+ self._raise_ssl_error(self._ssl, result)
+
+
+ def renegotiate_pending(self):
+ """
+ Check if there's a renegotiation in progress, it will return false once
+ a renegotiation is finished.
+
+ :return: Whether there's a renegotiation in progress
+ """
+
+ def total_renegotiations(self):
+ """
+ Find out the total number of renegotiations.
+
+ :return: The number of renegotiations.
+ """
+ return _lib.SSL_total_renegotiations(self._ssl)
+
+
+ def connect(self, addr):
+ """
+ Connect to remote host and set up client-side SSL
+
+ :param addr: A remote address
+ :return: What the socket's connect method returns
+ """
+ _lib.SSL_set_connect_state(self._ssl)
+ return self._socket.connect(addr)
+
+
+ def connect_ex(self, addr):
+ """
+ Connect to remote host and set up client-side SSL. Note that if the socket's
+ connect_ex method doesn't return 0, SSL won't be initialized.
+
+ :param addr: A remove address
+ :return: What the socket's connect_ex method returns
+ """
+ connect_ex = self._socket.connect_ex
+ self.set_connect_state()
+ return connect_ex(addr)
+
+
+ def accept(self):
+ """
+ Accept incoming connection and set up SSL on it
+
+ :return: A (conn,addr) pair where conn is a Connection and addr is an
+ address
+ """
+ client, addr = self._socket.accept()
+ conn = Connection(self._context, client)
+ conn.set_accept_state()
+ return (conn, addr)
+
+
+ def bio_shutdown(self):
+ """
+ When using non-socket connections this function signals end of
+ data on the input for this connection.
+
+ :return: None
+ """
+ if self._from_ssl is None:
+ raise TypeError("Connection sock was not None")
+
+ _lib.BIO_set_mem_eof_return(self._into_ssl, 0)
+
+
+ def shutdown(self):
+ """
+ Send closure alert
+
+ :return: True if the shutdown completed successfully (i.e. both sides
+ have sent closure alerts), false otherwise (i.e. you have to
+ wait for a ZeroReturnError on a recv() method call
+ """
+ result = _lib.SSL_shutdown(self._ssl)
+ if result < 0:
+ self._raise_ssl_error(self._ssl, result)
+ elif result > 0:
+ return True
+ else:
+ return False
+
+
+ def get_cipher_list(self):
+ """
+ Get the session cipher list
+
+ :return: A list of cipher strings
+ """
+ ciphers = []
+ for i in count():
+ result = _lib.SSL_get_cipher_list(self._ssl, i)
+ if result == _ffi.NULL:
+ break
+ ciphers.append(_native(_ffi.string(result)))
+ return ciphers
+
+
+ def get_client_ca_list(self):
+ """
+ Get CAs whose certificates are suggested for client authentication.
+
+ :return: If this is a server connection, a list of X509Names representing
+ the acceptable CAs as set by :py:meth:`OpenSSL.SSL.Context.set_client_ca_list` or
+ :py:meth:`OpenSSL.SSL.Context.add_client_ca`. If this is a client connection,
+ the list of such X509Names sent by the server, or an empty list if that
+ has not yet happened.
+ """
+ ca_names = _lib.SSL_get_client_CA_list(self._ssl)
+ if ca_names == _ffi.NULL:
+ # TODO: This is untested.
+ return []
+
+ result = []
+ for i in range(_lib.sk_X509_NAME_num(ca_names)):
+ name = _lib.sk_X509_NAME_value(ca_names, i)
+ copy = _lib.X509_NAME_dup(name)
+ if copy == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ pyname = X509Name.__new__(X509Name)
+ pyname._name = _ffi.gc(copy, _lib.X509_NAME_free)
+ result.append(pyname)
+ return result
+
+
+ def makefile(self):
+ """
+ The makefile() method is not implemented, since there is no dup semantics
+ for SSL connections
+
+ :raise: NotImplementedError
+ """
+ raise NotImplementedError("Cannot make file object of OpenSSL.SSL.Connection")
+
+
+ def get_app_data(self):
+ """
+ Get application data
+
+ :return: The application data
+ """
+ return self._app_data
+
+
+ def set_app_data(self, data):
+ """
+ Set application data
+
+ :param data - The application data
+ :return: None
+ """
+ self._app_data = data
+
+
+ def get_shutdown(self):
+ """
+ Get shutdown state
+
+ :return: The shutdown state, a bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN.
+ """
+ return _lib.SSL_get_shutdown(self._ssl)
+
+
+ def set_shutdown(self, state):
+ """
+ Set shutdown state
+
+ :param state - bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN.
+ :return: None
+ """
+ if not isinstance(state, integer_types):
+ raise TypeError("state must be an integer")
+
+ _lib.SSL_set_shutdown(self._ssl, state)
+
+
+ def state_string(self):
+ """
+ Get a verbose state description
+
+ :return: A string representing the state
+ """
+
+ def server_random(self):
+ """
+ Get a copy of the server hello nonce.
+
+ :return: A string representing the state
+ """
+ if self._ssl.session == _ffi.NULL:
+ return None
+ return _ffi.buffer(
+ self._ssl.s3.server_random,
+ _lib.SSL3_RANDOM_SIZE)[:]
+
+
+ def client_random(self):
+ """
+ Get a copy of the client hello nonce.
+
+ :return: A string representing the state
+ """
+ if self._ssl.session == _ffi.NULL:
+ return None
+ return _ffi.buffer(
+ self._ssl.s3.client_random,
+ _lib.SSL3_RANDOM_SIZE)[:]
+
+
+ def master_key(self):
+ """
+ Get a copy of the master key.
+
+ :return: A string representing the state
+ """
+ if self._ssl.session == _ffi.NULL:
+ return None
+ return _ffi.buffer(
+ self._ssl.session.master_key,
+ self._ssl.session.master_key_length)[:]
+
+
+ def sock_shutdown(self, *args, **kwargs):
+ """
+ See shutdown(2)
+
+ :return: What the socket's shutdown() method returns
+ """
+ return self._socket.shutdown(*args, **kwargs)
+
+
+ def get_peer_certificate(self):
+ """
+ Retrieve the other side's certificate (if any)
+
+ :return: The peer's certificate
+ """
+ cert = _lib.SSL_get_peer_certificate(self._ssl)
+ if cert != _ffi.NULL:
+ pycert = X509.__new__(X509)
+ pycert._x509 = _ffi.gc(cert, _lib.X509_free)
+ return pycert
+ return None
+
+
+ def get_peer_cert_chain(self):
+ """
+ Retrieve the other side's certificate (if any)
+
+ :return: A list of X509 instances giving the peer's certificate chain,
+ or None if it does not have one.
+ """
+ cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl)
+ if cert_stack == _ffi.NULL:
+ return None
+
+ result = []
+ for i in range(_lib.sk_X509_num(cert_stack)):
+ # TODO could incref instead of dup here
+ cert = _lib.X509_dup(_lib.sk_X509_value(cert_stack, i))
+ pycert = X509.__new__(X509)
+ pycert._x509 = _ffi.gc(cert, _lib.X509_free)
+ result.append(pycert)
+ return result
+
+
+ def want_read(self):
+ """
+ Checks if more data has to be read from the transport layer to complete an
+ operation.
+
+ :return: True iff more data has to be read
+ """
+ return _lib.SSL_want_read(self._ssl)
+
+
+ def want_write(self):
+ """
+ Checks if there is data to write to the transport layer to complete an
+ operation.
+
+ :return: True iff there is data to write
+ """
+ return _lib.SSL_want_write(self._ssl)
+
+
+ def set_accept_state(self):
+ """
+ Set the connection to work in server mode. The handshake will be handled
+ automatically by read/write.
+
+ :return: None
+ """
+ _lib.SSL_set_accept_state(self._ssl)
+
+
+ def set_connect_state(self):
+ """
+ Set the connection to work in client mode. The handshake will be handled
+ automatically by read/write.
+
+ :return: None
+ """
+ _lib.SSL_set_connect_state(self._ssl)
+
+
+ def get_session(self):
+ """
+ Returns the Session currently used.
+
+ @return: An instance of :py:class:`OpenSSL.SSL.Session` or :py:obj:`None` if
+ no session exists.
+ """
+ session = _lib.SSL_get1_session(self._ssl)
+ if session == _ffi.NULL:
+ return None
+
+ pysession = Session.__new__(Session)
+ pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free)
+ return pysession
+
+
+ def set_session(self, session):
+ """
+ Set the session to be used when the TLS/SSL connection is established.
+
+ :param session: A Session instance representing the session to use.
+ :returns: None
+ """
+ if not isinstance(session, Session):
+ raise TypeError("session must be a Session instance")
+
+ result = _lib.SSL_set_session(self._ssl, session._session)
+ if not result:
+ _raise_current_error()
+
+
+ def _get_finished_message(self, function):
+ """
+ Helper to implement :py:meth:`get_finished` and
+ :py:meth:`get_peer_finished`.
+
+ :param function: Either :py:data:`SSL_get_finished`: or
+ :py:data:`SSL_get_peer_finished`.
+
+ :return: :py:data:`None` if the desired message has not yet been
+ received, otherwise the contents of the message.
+ :rtype: :py:class:`bytes` or :py:class:`NoneType`
+ """
+ # The OpenSSL documentation says nothing about what might happen if the
+ # count argument given is zero. Specifically, it doesn't say whether
+ # the output buffer may be NULL in that case or not. Inspection of the
+ # implementation reveals that it calls memcpy() unconditionally.
+ # Section 7.1.4, paragraph 1 of the C standard suggests that
+ # memcpy(NULL, source, 0) is not guaranteed to produce defined (let
+ # alone desirable) behavior (though it probably does on just about
+ # every implementation...)
+ #
+ # Allocate a tiny buffer to pass in (instead of just passing NULL as
+ # one might expect) for the initial call so as to be safe against this
+ # potentially undefined behavior.
+ empty = _ffi.new("char[]", 0)
+ size = function(self._ssl, empty, 0)
+ if size == 0:
+ # No Finished message so far.
+ return None
+
+ buf = _ffi.new("char[]", size)
+ function(self._ssl, buf, size)
+ return _ffi.buffer(buf, size)[:]
+
+
+ def get_finished(self):
+ """
+ Obtain the latest `handshake finished` message sent to the peer.
+
+ :return: The contents of the message or :py:obj:`None` if the TLS
+ handshake has not yet completed.
+ :rtype: :py:class:`bytes` or :py:class:`NoneType`
+ """
+ return self._get_finished_message(_lib.SSL_get_finished)
+
+
+ def get_peer_finished(self):
+ """
+ Obtain the latest `handshake finished` message received from the peer.
+
+ :return: The contents of the message or :py:obj:`None` if the TLS
+ handshake has not yet completed.
+ :rtype: :py:class:`bytes` or :py:class:`NoneType`
+ """
+ return self._get_finished_message(_lib.SSL_get_peer_finished)
+
+
+ def get_cipher_name(self):
+ """
+ Obtain the name of the currently used cipher.
+
+ :returns: The name of the currently used cipher or :py:obj:`None`
+ if no connection has been established.
+ :rtype: :py:class:`unicode` or :py:class:`NoneType`
+ """
+ cipher = _lib.SSL_get_current_cipher(self._ssl)
+ if cipher == _ffi.NULL:
+ return None
+ else:
+ name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher))
+ return name.decode("utf-8")
+
+
+ def get_cipher_bits(self):
+ """
+ Obtain the number of secret bits of the currently used cipher.
+
+ :returns: The number of secret bits of the currently used cipher
+ or :py:obj:`None` if no connection has been established.
+ :rtype: :py:class:`int` or :py:class:`NoneType`
+ """
+ cipher = _lib.SSL_get_current_cipher(self._ssl)
+ if cipher == _ffi.NULL:
+ return None
+ else:
+ return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL)
+
+
+ def get_cipher_version(self):
+ """
+ Obtain the protocol version of the currently used cipher.
+
+ :returns: The protocol name of the currently used cipher
+ or :py:obj:`None` if no connection has been established.
+ :rtype: :py:class:`unicode` or :py:class:`NoneType`
+ """
+ cipher = _lib.SSL_get_current_cipher(self._ssl)
+ if cipher == _ffi.NULL:
+ return None
+ else:
+ version =_ffi.string(_lib.SSL_CIPHER_get_version(cipher))
+ return version.decode("utf-8")
+
+
+ @_requires_npn
+ def get_next_proto_negotiated(self):
+ """
+ Get the protocol that was negotiated by NPN.
+ """
+ data = _ffi.new("unsigned char **")
+ data_len = _ffi.new("unsigned int *")
+
+ _lib.SSL_get0_next_proto_negotiated(self._ssl, data, data_len)
+
+ return _ffi.buffer(data[0], data_len[0])[:]
+
+ @_requires_alpn
+ def set_alpn_protos(self, protos):
+ """
+ Specify the client's ALPN protocol list.
+
+ These protocols are offered to the server during protocol negotiation.
+
+ :param protos: A list of the protocols to be offered to the server.
+ This list should be a Python list of bytestrings representing the
+ protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``.
+ """
+ # Take the list of protocols and join them together, prefixing them
+ # with their lengths.
+ protostr = b''.join(
+ chain.from_iterable((int2byte(len(p)), p) for p in protos)
+ )
+
+ # Build a C string from the list. We don't need to save this off
+ # because OpenSSL immediately copies the data out.
+ input_str = _ffi.new("unsigned char[]", protostr)
+ input_str_len = _ffi.cast("unsigned", len(protostr))
+ _lib.SSL_set_alpn_protos(self._ssl, input_str, input_str_len)
+
+
+ def get_alpn_proto_negotiated(self):
+ """
+ Get the protocol that was negotiated by ALPN.
+ """
+ if not _lib.Cryptography_HAS_ALPN:
+ raise NotImplementedError("ALPN not available")
+
+ data = _ffi.new("unsigned char **")
+ data_len = _ffi.new("unsigned int *")
+
+ _lib.SSL_get0_alpn_selected(self._ssl, data, data_len)
+
+ if not data_len:
+ return b''
+
+ return _ffi.buffer(data[0], data_len[0])[:]
+
+
+
+ConnectionType = Connection
+
+# This is similar to the initialization calls at the end of OpenSSL/crypto.py
+# but is exercised mostly by the Context initializer.
+_lib.SSL_library_init()
diff --git a/lib/Python/Lib/OpenSSL/__init__.py b/lib/Python/Lib/OpenSSL/__init__.py
new file mode 100644
index 000000000..db96e1fbd
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (C) AB Strakt
+# See LICENSE for details.
+
+"""
+pyOpenSSL - A simple wrapper around the OpenSSL library
+"""
+
+from OpenSSL import rand, crypto, SSL
+from OpenSSL.version import __version__
+
+__all__ = [
+ 'rand', 'crypto', 'SSL', 'tsafe', '__version__']
diff --git a/lib/Python/Lib/OpenSSL/_util.py b/lib/Python/Lib/OpenSSL/_util.py
new file mode 100644
index 000000000..0cc34d80c
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/_util.py
@@ -0,0 +1,127 @@
+from warnings import warn
+import sys
+
+from six import PY3, binary_type, text_type
+
+from cryptography.hazmat.bindings.openssl.binding import Binding
+binding = Binding()
+ffi = binding.ffi
+lib = binding.lib
+
+
+
+def text(charp):
+ """
+ Get a native string type representing of the given CFFI ``char*`` object.
+
+ :param charp: A C-style string represented using CFFI.
+
+ :return: :class:`str`
+ """
+ if not charp:
+ return ""
+ return native(ffi.string(charp))
+
+
+
+def exception_from_error_queue(exception_type):
+ """
+ Convert an OpenSSL library failure into a Python exception.
+
+ When a call to the native OpenSSL library fails, this is usually signalled
+ by the return value, and an error code is stored in an error queue
+ associated with the current thread. The err library provides functions to
+ obtain these error codes and textual error messages.
+ """
+
+ errors = []
+
+ while True:
+ error = lib.ERR_get_error()
+ if error == 0:
+ break
+ errors.append((
+ text(lib.ERR_lib_error_string(error)),
+ text(lib.ERR_func_error_string(error)),
+ text(lib.ERR_reason_error_string(error))))
+
+ raise exception_type(errors)
+
+
+
+def native(s):
+ """
+ Convert :py:class:`bytes` or :py:class:`unicode` to the native
+ :py:class:`str` type, using UTF-8 encoding if conversion is necessary.
+
+ :raise UnicodeError: The input string is not UTF-8 decodeable.
+
+ :raise TypeError: The input is neither :py:class:`bytes` nor
+ :py:class:`unicode`.
+ """
+ if not isinstance(s, (binary_type, text_type)):
+ raise TypeError("%r is neither bytes nor unicode" % s)
+ if PY3:
+ if isinstance(s, binary_type):
+ return s.decode("utf-8")
+ else:
+ if isinstance(s, text_type):
+ return s.encode("utf-8")
+ return s
+
+
+
+def path_string(s):
+ """
+ Convert a Python string to a :py:class:`bytes` string identifying the same
+ path and which can be passed into an OpenSSL API accepting a filename.
+
+ :param s: An instance of :py:class:`bytes` or :py:class:`unicode`.
+
+ :return: An instance of :py:class:`bytes`.
+ """
+ if isinstance(s, binary_type):
+ return s
+ elif isinstance(s, text_type):
+ return s.encode(sys.getfilesystemencoding())
+ else:
+ raise TypeError("Path must be represented as bytes or unicode string")
+
+
+if PY3:
+ def byte_string(s):
+ return s.encode("charmap")
+else:
+ def byte_string(s):
+ return s
+
+
+# A marker object to observe whether some optional arguments are passed any
+# value or not.
+UNSPECIFIED = object()
+
+_TEXT_WARNING = (
+ text_type.__name__ + " for {0} is no longer accepted, use bytes"
+)
+
+def text_to_bytes_and_warn(label, obj):
+ """
+ If ``obj`` is text, emit a warning that it should be bytes instead and try
+ to convert it to bytes automatically.
+
+ :param str label: The name of the parameter from which ``obj`` was taken
+ (so a developer can easily find the source of the problem and correct
+ it).
+
+ :return: If ``obj`` is the text string type, a ``bytes`` object giving the
+ UTF-8 encoding of that text is returned. Otherwise, ``obj`` itself is
+ returned.
+ """
+ if isinstance(obj, text_type):
+ warn(
+ _TEXT_WARNING.format(label),
+ category=DeprecationWarning,
+ stacklevel=3
+ )
+ return obj.encode('utf-8')
+ return obj
diff --git a/lib/Python/Lib/OpenSSL/crypto.py b/lib/Python/Lib/OpenSSL/crypto.py
new file mode 100644
index 000000000..50ff74fad
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/crypto.py
@@ -0,0 +1,2639 @@
+from time import time
+from base64 import b16encode
+from functools import partial
+from operator import __eq__, __ne__, __lt__, __le__, __gt__, __ge__
+from warnings import warn as _warn
+
+from six import (
+ integer_types as _integer_types,
+ text_type as _text_type,
+ PY3 as _PY3)
+
+from OpenSSL._util import (
+ ffi as _ffi,
+ lib as _lib,
+ exception_from_error_queue as _exception_from_error_queue,
+ byte_string as _byte_string,
+ native as _native,
+ UNSPECIFIED as _UNSPECIFIED,
+ text_to_bytes_and_warn as _text_to_bytes_and_warn,
+)
+
+FILETYPE_PEM = _lib.SSL_FILETYPE_PEM
+FILETYPE_ASN1 = _lib.SSL_FILETYPE_ASN1
+
+# TODO This was an API mistake. OpenSSL has no such constant.
+FILETYPE_TEXT = 2 ** 16 - 1
+
+TYPE_RSA = _lib.EVP_PKEY_RSA
+TYPE_DSA = _lib.EVP_PKEY_DSA
+
+
+
+class Error(Exception):
+ """
+ An error occurred in an `OpenSSL.crypto` API.
+ """
+
+
+_raise_current_error = partial(_exception_from_error_queue, Error)
+
+
+
+def _untested_error(where):
+ """
+ An OpenSSL API failed somehow. Additionally, the failure which was
+ encountered isn't one that's exercised by the test suite so future behavior
+ of pyOpenSSL is now somewhat less predictable.
+ """
+ raise RuntimeError("Unknown %s failure" % (where,))
+
+
+
+def _new_mem_buf(buffer=None):
+ """
+ Allocate a new OpenSSL memory BIO.
+
+ Arrange for the garbage collector to clean it up automatically.
+
+ :param buffer: None or some bytes to use to put into the BIO so that they
+ can be read out.
+ """
+ if buffer is None:
+ bio = _lib.BIO_new(_lib.BIO_s_mem())
+ free = _lib.BIO_free
+ else:
+ data = _ffi.new("char[]", buffer)
+ bio = _lib.BIO_new_mem_buf(data, len(buffer))
+ # Keep the memory alive as long as the bio is alive!
+ def free(bio, ref=data):
+ return _lib.BIO_free(bio)
+
+ if bio == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ bio = _ffi.gc(bio, free)
+ return bio
+
+
+
+def _bio_to_string(bio):
+ """
+ Copy the contents of an OpenSSL BIO object into a Python byte string.
+ """
+ result_buffer = _ffi.new('char**')
+ buffer_length = _lib.BIO_get_mem_data(bio, result_buffer)
+ return _ffi.buffer(result_buffer[0], buffer_length)[:]
+
+
+
+def _set_asn1_time(boundary, when):
+ """
+ The the time value of an ASN1 time object.
+
+ @param boundary: An ASN1_GENERALIZEDTIME pointer (or an object safely
+ castable to that type) which will have its value set.
+ @param when: A string representation of the desired time value.
+
+ @raise TypeError: If C{when} is not a L{bytes} string.
+ @raise ValueError: If C{when} does not represent a time in the required
+ format.
+ @raise RuntimeError: If the time value cannot be set for some other
+ (unspecified) reason.
+ """
+ if not isinstance(when, bytes):
+ raise TypeError("when must be a byte string")
+
+ set_result = _lib.ASN1_GENERALIZEDTIME_set_string(
+ _ffi.cast('ASN1_GENERALIZEDTIME*', boundary), when)
+ if set_result == 0:
+ dummy = _ffi.gc(_lib.ASN1_STRING_new(), _lib.ASN1_STRING_free)
+ _lib.ASN1_STRING_set(dummy, when, len(when))
+ check_result = _lib.ASN1_GENERALIZEDTIME_check(
+ _ffi.cast('ASN1_GENERALIZEDTIME*', dummy))
+ if not check_result:
+ raise ValueError("Invalid string")
+ else:
+ _untested_error()
+
+
+
+def _get_asn1_time(timestamp):
+ """
+ Retrieve the time value of an ASN1 time object.
+
+ @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to
+ that type) from which the time value will be retrieved.
+
+ @return: The time value from C{timestamp} as a L{bytes} string in a certain
+ format. Or C{None} if the object contains no time value.
+ """
+ string_timestamp = _ffi.cast('ASN1_STRING*', timestamp)
+ if _lib.ASN1_STRING_length(string_timestamp) == 0:
+ return None
+ elif _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME:
+ return _ffi.string(_lib.ASN1_STRING_data(string_timestamp))
+ else:
+ generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**")
+ _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp)
+ if generalized_timestamp[0] == _ffi.NULL:
+ # This may happen:
+ # - if timestamp was not an ASN1_TIME
+ # - if allocating memory for the ASN1_GENERALIZEDTIME failed
+ # - if a copy of the time data from timestamp cannot be made for
+ # the newly allocated ASN1_GENERALIZEDTIME
+ #
+ # These are difficult to test. cffi enforces the ASN1_TIME type.
+ # Memory allocation failures are a pain to trigger
+ # deterministically.
+ _untested_error("ASN1_TIME_to_generalizedtime")
+ else:
+ string_timestamp = _ffi.cast(
+ "ASN1_STRING*", generalized_timestamp[0])
+ string_data = _lib.ASN1_STRING_data(string_timestamp)
+ string_result = _ffi.string(string_data)
+ _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0])
+ return string_result
+
+
+
+class PKey(object):
+ _only_public = False
+ _initialized = True
+
+ def __init__(self):
+ pkey = _lib.EVP_PKEY_new()
+ self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free)
+ self._initialized = False
+
+
+ def generate_key(self, type, bits):
+ """
+ Generate a key of a given type, with a given number of a bits
+
+ :param type: The key type (TYPE_RSA or TYPE_DSA)
+ :param bits: The number of bits
+
+ :return: None
+ """
+ if not isinstance(type, int):
+ raise TypeError("type must be an integer")
+
+ if not isinstance(bits, int):
+ raise TypeError("bits must be an integer")
+
+ # TODO Check error return
+ exponent = _lib.BN_new()
+ exponent = _ffi.gc(exponent, _lib.BN_free)
+ _lib.BN_set_word(exponent, _lib.RSA_F4)
+
+ if type == TYPE_RSA:
+ if bits <= 0:
+ raise ValueError("Invalid number of bits")
+
+ rsa = _lib.RSA_new()
+
+ result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL)
+ if result == 0:
+ # TODO: The test for this case is commented out. Different
+ # builds of OpenSSL appear to have different failure modes that
+ # make it hard to test. Visual inspection of the OpenSSL
+ # source reveals that a return value of 0 signals an error.
+ # Manual testing on a particular build of OpenSSL suggests that
+ # this is probably the appropriate way to handle those errors.
+ _raise_current_error()
+
+ result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa)
+ if not result:
+ # TODO: It appears as though this can fail if an engine is in
+ # use which does not support RSA.
+ _raise_current_error()
+
+ elif type == TYPE_DSA:
+ dsa = _lib.DSA_generate_parameters(
+ bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL)
+ if dsa == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ if not _lib.DSA_generate_key(dsa):
+ # TODO: This is untested.
+ _raise_current_error()
+ if not _lib.EVP_PKEY_assign_DSA(self._pkey, dsa):
+ # TODO: This is untested.
+ _raise_current_error()
+ else:
+ raise Error("No such key type")
+
+ self._initialized = True
+
+
+ def check(self):
+ """
+ Check the consistency of an RSA private key.
+
+ :return: True if key is consistent.
+ :raise Error: if the key is inconsistent.
+ :raise TypeError: if the key is of a type which cannot be checked.
+ Only RSA keys can currently be checked.
+ """
+ if self._only_public:
+ raise TypeError("public key only")
+
+ if _lib.EVP_PKEY_type(self._pkey.type) != _lib.EVP_PKEY_RSA:
+ raise TypeError("key type unsupported")
+
+ rsa = _lib.EVP_PKEY_get1_RSA(self._pkey)
+ rsa = _ffi.gc(rsa, _lib.RSA_free)
+ result = _lib.RSA_check_key(rsa)
+ if result:
+ return True
+ _raise_current_error()
+
+
+ def type(self):
+ """
+ Returns the type of the key
+
+ :return: The type of the key.
+ """
+ return self._pkey.type
+
+
+ def bits(self):
+ """
+ Returns the number of bits of the key
+
+ :return: The number of bits of the key.
+ """
+ return _lib.EVP_PKEY_bits(self._pkey)
+PKeyType = PKey
+
+
+
+class _EllipticCurve(object):
+ """
+ A representation of a supported elliptic curve.
+
+ @cvar _curves: :py:obj:`None` until an attempt is made to load the curves.
+ Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve`
+ instances each of which represents one curve supported by the system.
+ @type _curves: :py:type:`NoneType` or :py:type:`set`
+ """
+ _curves = None
+
+ if _PY3:
+ # This only necessary on Python 3. Morever, it is broken on Python 2.
+ def __ne__(self, other):
+ """
+ Implement cooperation with the right-hand side argument of ``!=``.
+
+ Python 3 seems to have dropped this cooperation in this very narrow
+ circumstance.
+ """
+ if isinstance(other, _EllipticCurve):
+ return super(_EllipticCurve, self).__ne__(other)
+ return NotImplemented
+
+
+ @classmethod
+ def _load_elliptic_curves(cls, lib):
+ """
+ Get the curves supported by OpenSSL.
+
+ :param lib: The OpenSSL library binding object.
+
+ :return: A :py:type:`set` of ``cls`` instances giving the names of the
+ elliptic curves the underlying library supports.
+ """
+ if lib.Cryptography_HAS_EC:
+ num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0)
+ builtin_curves = _ffi.new('EC_builtin_curve[]', num_curves)
+ # The return value on this call should be num_curves again. We could
+ # check it to make sure but if it *isn't* then.. what could we do?
+ # Abort the whole process, I suppose...? -exarkun
+ lib.EC_get_builtin_curves(builtin_curves, num_curves)
+ return set(
+ cls.from_nid(lib, c.nid)
+ for c in builtin_curves)
+ return set()
+
+
+ @classmethod
+ def _get_elliptic_curves(cls, lib):
+ """
+ Get, cache, and return the curves supported by OpenSSL.
+
+ :param lib: The OpenSSL library binding object.
+
+ :return: A :py:type:`set` of ``cls`` instances giving the names of the
+ elliptic curves the underlying library supports.
+ """
+ if cls._curves is None:
+ cls._curves = cls._load_elliptic_curves(lib)
+ return cls._curves
+
+
+ @classmethod
+ def from_nid(cls, lib, nid):
+ """
+ Instantiate a new :py:class:`_EllipticCurve` associated with the given
+ OpenSSL NID.
+
+ :param lib: The OpenSSL library binding object.
+
+ :param nid: The OpenSSL NID the resulting curve object will represent.
+ This must be a curve NID (and not, for example, a hash NID) or
+ subsequent operations will fail in unpredictable ways.
+ :type nid: :py:class:`int`
+
+ :return: The curve object.
+ """
+ return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii"))
+
+
+ def __init__(self, lib, nid, name):
+ """
+ :param _lib: The :py:mod:`cryptography` binding instance used to
+ interface with OpenSSL.
+
+ :param _nid: The OpenSSL NID identifying the curve this object
+ represents.
+ :type _nid: :py:class:`int`
+
+ :param name: The OpenSSL short name identifying the curve this object
+ represents.
+ :type name: :py:class:`unicode`
+ """
+ self._lib = lib
+ self._nid = nid
+ self.name = name
+
+
+ def __repr__(self):
+ return "<Curve %r>" % (self.name,)
+
+
+ def _to_EC_KEY(self):
+ """
+ Create a new OpenSSL EC_KEY structure initialized to use this curve.
+
+ The structure is automatically garbage collected when the Python object
+ is garbage collected.
+ """
+ key = self._lib.EC_KEY_new_by_curve_name(self._nid)
+ return _ffi.gc(key, _lib.EC_KEY_free)
+
+
+
+def get_elliptic_curves():
+ """
+ Return a set of objects representing the elliptic curves supported in the
+ OpenSSL build in use.
+
+ The curve objects have a :py:class:`unicode` ``name`` attribute by which
+ they identify themselves.
+
+ The curve objects are useful as values for the argument accepted by
+ :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be
+ used for ECDHE key exchange.
+ """
+ return _EllipticCurve._get_elliptic_curves(_lib)
+
+
+
+def get_elliptic_curve(name):
+ """
+ Return a single curve object selected by name.
+
+ See :py:func:`get_elliptic_curves` for information about curve objects.
+
+ :param name: The OpenSSL short name identifying the curve object to
+ retrieve.
+ :type name: :py:class:`unicode`
+
+ If the named curve is not supported then :py:class:`ValueError` is raised.
+ """
+ for curve in get_elliptic_curves():
+ if curve.name == name:
+ return curve
+ raise ValueError("unknown curve name", name)
+
+
+
+class X509Name(object):
+ def __init__(self, name):
+ """
+ Create a new X509Name, copying the given X509Name instance.
+
+ :param name: An X509Name object to copy
+ """
+ name = _lib.X509_NAME_dup(name._name)
+ self._name = _ffi.gc(name, _lib.X509_NAME_free)
+
+
+ def __setattr__(self, name, value):
+ if name.startswith('_'):
+ return super(X509Name, self).__setattr__(name, value)
+
+ # Note: we really do not want str subclasses here, so we do not use
+ # isinstance.
+ if type(name) is not str:
+ raise TypeError("attribute name must be string, not '%.200s'" % (
+ type(value).__name__,))
+
+ nid = _lib.OBJ_txt2nid(_byte_string(name))
+ if nid == _lib.NID_undef:
+ try:
+ _raise_current_error()
+ except Error:
+ pass
+ raise AttributeError("No such attribute")
+
+ # If there's an old entry for this NID, remove it
+ for i in range(_lib.X509_NAME_entry_count(self._name)):
+ ent = _lib.X509_NAME_get_entry(self._name, i)
+ ent_obj = _lib.X509_NAME_ENTRY_get_object(ent)
+ ent_nid = _lib.OBJ_obj2nid(ent_obj)
+ if nid == ent_nid:
+ ent = _lib.X509_NAME_delete_entry(self._name, i)
+ _lib.X509_NAME_ENTRY_free(ent)
+ break
+
+ if isinstance(value, _text_type):
+ value = value.encode('utf-8')
+
+ add_result = _lib.X509_NAME_add_entry_by_NID(
+ self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0)
+ if not add_result:
+ _raise_current_error()
+
+
+ def __getattr__(self, name):
+ """
+ Find attribute. An X509Name object has the following attributes:
+ countryName (alias C), stateOrProvince (alias ST), locality (alias L),
+ organization (alias O), organizationalUnit (alias OU), commonName (alias
+ CN) and more...
+ """
+ nid = _lib.OBJ_txt2nid(_byte_string(name))
+ if nid == _lib.NID_undef:
+ # This is a bit weird. OBJ_txt2nid indicated failure, but it seems
+ # a lower level function, a2d_ASN1_OBJECT, also feels the need to
+ # push something onto the error queue. If we don't clean that up
+ # now, someone else will bump into it later and be quite confused.
+ # See lp#314814.
+ try:
+ _raise_current_error()
+ except Error:
+ pass
+ return super(X509Name, self).__getattr__(name)
+
+ entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1)
+ if entry_index == -1:
+ return None
+
+ entry = _lib.X509_NAME_get_entry(self._name, entry_index)
+ data = _lib.X509_NAME_ENTRY_get_data(entry)
+
+ result_buffer = _ffi.new("unsigned char**")
+ data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data)
+ if data_length < 0:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ try:
+ result = _ffi.buffer(result_buffer[0], data_length)[:].decode('utf-8')
+ finally:
+ # XXX untested
+ _lib.OPENSSL_free(result_buffer[0])
+ return result
+
+
+ def _cmp(op):
+ def f(self, other):
+ if not isinstance(other, X509Name):
+ return NotImplemented
+ result = _lib.X509_NAME_cmp(self._name, other._name)
+ return op(result, 0)
+ return f
+
+ __eq__ = _cmp(__eq__)
+ __ne__ = _cmp(__ne__)
+
+ __lt__ = _cmp(__lt__)
+ __le__ = _cmp(__le__)
+
+ __gt__ = _cmp(__gt__)
+ __ge__ = _cmp(__ge__)
+
+ def __repr__(self):
+ """
+ String representation of an X509Name
+ """
+ result_buffer = _ffi.new("char[]", 512);
+ format_result = _lib.X509_NAME_oneline(
+ self._name, result_buffer, len(result_buffer))
+
+ if format_result == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return "<X509Name object '%s'>" % (
+ _native(_ffi.string(result_buffer)),)
+
+
+ def hash(self):
+ """
+ Return the hash value of this name
+
+ :return: None
+ """
+ return _lib.X509_NAME_hash(self._name)
+
+
+ def der(self):
+ """
+ Return the DER encoding of this name
+
+ :return: A :py:class:`bytes` instance giving the DER encoded form of
+ this name.
+ """
+ result_buffer = _ffi.new('unsigned char**')
+ encode_result = _lib.i2d_X509_NAME(self._name, result_buffer)
+ if encode_result < 0:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ string_result = _ffi.buffer(result_buffer[0], encode_result)[:]
+ _lib.OPENSSL_free(result_buffer[0])
+ return string_result
+
+
+ def get_components(self):
+ """
+ Returns the split-up components of this name.
+
+ :return: List of tuples (name, value).
+ """
+ result = []
+ for i in range(_lib.X509_NAME_entry_count(self._name)):
+ ent = _lib.X509_NAME_get_entry(self._name, i)
+
+ fname = _lib.X509_NAME_ENTRY_get_object(ent)
+ fval = _lib.X509_NAME_ENTRY_get_data(ent)
+
+ nid = _lib.OBJ_obj2nid(fname)
+ name = _lib.OBJ_nid2sn(nid)
+
+ result.append((
+ _ffi.string(name),
+ _ffi.string(
+ _lib.ASN1_STRING_data(fval),
+ _lib.ASN1_STRING_length(fval))))
+
+ return result
+X509NameType = X509Name
+
+
+class X509Extension(object):
+ def __init__(self, type_name, critical, value, subject=None, issuer=None):
+ """
+ :param typename: The name of the extension to create.
+ :type typename: :py:data:`str`
+
+ :param critical: A flag indicating whether this is a critical extension.
+
+ :param value: The value of the extension.
+ :type value: :py:data:`str`
+
+ :param subject: Optional X509 cert to use as subject.
+ :type subject: :py:class:`X509`
+
+ :param issuer: Optional X509 cert to use as issuer.
+ :type issuer: :py:class:`X509`
+
+ :return: The X509Extension object
+ """
+ ctx = _ffi.new("X509V3_CTX*")
+
+ # A context is necessary for any extension which uses the r2i conversion
+ # method. That is, X509V3_EXT_nconf may segfault if passed a NULL ctx.
+ # Start off by initializing most of the fields to NULL.
+ _lib.X509V3_set_ctx(ctx, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL, 0)
+
+ # We have no configuration database - but perhaps we should (some
+ # extensions may require it).
+ _lib.X509V3_set_ctx_nodb(ctx)
+
+ # Initialize the subject and issuer, if appropriate. ctx is a local,
+ # and as far as I can tell none of the X509V3_* APIs invoked here steal
+ # any references, so no need to mess with reference counts or duplicates.
+ if issuer is not None:
+ if not isinstance(issuer, X509):
+ raise TypeError("issuer must be an X509 instance")
+ ctx.issuer_cert = issuer._x509
+ if subject is not None:
+ if not isinstance(subject, X509):
+ raise TypeError("subject must be an X509 instance")
+ ctx.subject_cert = subject._x509
+
+ if critical:
+ # There are other OpenSSL APIs which would let us pass in critical
+ # separately, but they're harder to use, and since value is already
+ # a pile of crappy junk smuggling a ton of utterly important
+ # structured data, what's the point of trying to avoid nasty stuff
+ # with strings? (However, X509V3_EXT_i2d in particular seems like it
+ # would be a better API to invoke. I do not know where to get the
+ # ext_struc it desires for its last parameter, though.)
+ value = b"critical," + value
+
+ extension = _lib.X509V3_EXT_nconf(_ffi.NULL, ctx, type_name, value)
+ if extension == _ffi.NULL:
+ _raise_current_error()
+ self._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free)
+
+
+ @property
+ def _nid(self):
+ return _lib.OBJ_obj2nid(self._extension.object)
+
+ _prefixes = {
+ _lib.GEN_EMAIL: "email",
+ _lib.GEN_DNS: "DNS",
+ _lib.GEN_URI: "URI",
+ }
+
+ def _subjectAltNameString(self):
+ method = _lib.X509V3_EXT_get(self._extension)
+ if method == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ payload = self._extension.value.data
+ length = self._extension.value.length
+
+ payloadptr = _ffi.new("unsigned char**")
+ payloadptr[0] = payload
+
+ if method.it != _ffi.NULL:
+ ptr = _lib.ASN1_ITEM_ptr(method.it)
+ data = _lib.ASN1_item_d2i(_ffi.NULL, payloadptr, length, ptr)
+ names = _ffi.cast("GENERAL_NAMES*", data)
+ else:
+ names = _ffi.cast(
+ "GENERAL_NAMES*",
+ method.d2i(_ffi.NULL, payloadptr, length))
+
+ parts = []
+ for i in range(_lib.sk_GENERAL_NAME_num(names)):
+ name = _lib.sk_GENERAL_NAME_value(names, i)
+ try:
+ label = self._prefixes[name.type]
+ except KeyError:
+ bio = _new_mem_buf()
+ _lib.GENERAL_NAME_print(bio, name)
+ parts.append(_native(_bio_to_string(bio)))
+ else:
+ value = _native(
+ _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[:])
+ parts.append(label + ":" + value)
+ return ", ".join(parts)
+
+
+ def __str__(self):
+ """
+ :return: a nice text representation of the extension
+ """
+ if _lib.NID_subject_alt_name == self._nid:
+ return self._subjectAltNameString()
+
+ bio = _new_mem_buf()
+ print_result = _lib.X509V3_EXT_print(bio, self._extension, 0, 0)
+ if not print_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return _native(_bio_to_string(bio))
+
+
+ def get_critical(self):
+ """
+ Returns the critical field of the X509Extension
+
+ :return: The critical field.
+ """
+ return _lib.X509_EXTENSION_get_critical(self._extension)
+
+
+ def get_short_name(self):
+ """
+ Returns the short version of the type name of the X509Extension
+
+ :return: The short type name.
+ """
+ obj = _lib.X509_EXTENSION_get_object(self._extension)
+ nid = _lib.OBJ_obj2nid(obj)
+ return _ffi.string(_lib.OBJ_nid2sn(nid))
+
+
+ def get_data(self):
+ """
+ Returns the data of the X509Extension
+
+ :return: A :py:data:`str` giving the X509Extension's ASN.1 encoded data.
+ """
+ octet_result = _lib.X509_EXTENSION_get_data(self._extension)
+ string_result = _ffi.cast('ASN1_STRING*', octet_result)
+ char_result = _lib.ASN1_STRING_data(string_result)
+ result_length = _lib.ASN1_STRING_length(string_result)
+ return _ffi.buffer(char_result, result_length)[:]
+
+X509ExtensionType = X509Extension
+
+
+class X509Req(object):
+ def __init__(self):
+ req = _lib.X509_REQ_new()
+ self._req = _ffi.gc(req, _lib.X509_REQ_free)
+
+
+ def set_pubkey(self, pkey):
+ """
+ Set the public key of the certificate request
+
+ :param pkey: The public key to use
+ :return: None
+ """
+ set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey)
+ if not set_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def get_pubkey(self):
+ """
+ Get the public key from the certificate request
+
+ :return: The public key
+ """
+ pkey = PKey.__new__(PKey)
+ pkey._pkey = _lib.X509_REQ_get_pubkey(self._req)
+ if pkey._pkey == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free)
+ pkey._only_public = True
+ return pkey
+
+
+ def set_version(self, version):
+ """
+ Set the version subfield (RFC 2459, section 4.1.2.1) of the certificate
+ request.
+
+ :param version: The version number
+ :return: None
+ """
+ set_result = _lib.X509_REQ_set_version(self._req, version)
+ if not set_result:
+ _raise_current_error()
+
+
+ def get_version(self):
+ """
+ Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate
+ request.
+
+ :return: an integer giving the value of the version subfield
+ """
+ return _lib.X509_REQ_get_version(self._req)
+
+
+ def get_subject(self):
+ """
+ Create an X509Name object for the subject of the certificate request
+
+ :return: An X509Name object
+ """
+ name = X509Name.__new__(X509Name)
+ name._name = _lib.X509_REQ_get_subject_name(self._req)
+ if name._name == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ # The name is owned by the X509Req structure. As long as the X509Name
+ # Python object is alive, keep the X509Req Python object alive.
+ name._owner = self
+
+ return name
+
+
+ def add_extensions(self, extensions):
+ """
+ Add extensions to the request.
+
+ :param extensions: a sequence of X509Extension objects
+ :return: None
+ """
+ stack = _lib.sk_X509_EXTENSION_new_null()
+ if stack == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ stack = _ffi.gc(stack, _lib.sk_X509_EXTENSION_free)
+
+ for ext in extensions:
+ if not isinstance(ext, X509Extension):
+ raise ValueError("One of the elements is not an X509Extension")
+
+ # TODO push can fail (here and elsewhere)
+ _lib.sk_X509_EXTENSION_push(stack, ext._extension)
+
+ add_result = _lib.X509_REQ_add_extensions(self._req, stack)
+ if not add_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def get_extensions(self):
+ """
+ Get extensions to the request.
+
+ :return: A :py:class:`list` of :py:class:`X509Extension` objects.
+ """
+ exts = []
+ native_exts_obj = _lib.X509_REQ_get_extensions(self._req)
+ for i in range(_lib.sk_X509_EXTENSION_num(native_exts_obj)):
+ ext = X509Extension.__new__(X509Extension)
+ ext._extension = _lib.sk_X509_EXTENSION_value(native_exts_obj, i)
+ exts.append(ext)
+ return exts
+
+
+ def sign(self, pkey, digest):
+ """
+ Sign the certificate request using the supplied key and digest
+
+ :param pkey: The key to sign with
+ :param digest: The message digest to use
+ :return: None
+ """
+ if pkey._only_public:
+ raise ValueError("Key has only public part")
+
+ if not pkey._initialized:
+ raise ValueError("Key is uninitialized")
+
+ digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest))
+ if digest_obj == _ffi.NULL:
+ raise ValueError("No such digest method")
+
+ sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj)
+ if not sign_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def verify(self, pkey):
+ """
+ Verifies a certificate request using the supplied public key
+
+ :param key: a public key
+ :return: True if the signature is correct.
+
+ :raise OpenSSL.crypto.Error: If the signature is invalid or there is a
+ problem verifying the signature.
+ """
+ if not isinstance(pkey, PKey):
+ raise TypeError("pkey must be a PKey instance")
+
+ result = _lib.X509_REQ_verify(self._req, pkey._pkey)
+ if result <= 0:
+ _raise_current_error()
+
+ return result
+
+
+X509ReqType = X509Req
+
+
+
+class X509(object):
+ def __init__(self):
+ # TODO Allocation failure? And why not __new__ instead of __init__?
+ x509 = _lib.X509_new()
+ self._x509 = _ffi.gc(x509, _lib.X509_free)
+
+
+ def set_version(self, version):
+ """
+ Set version number of the certificate
+
+ :param version: The version number
+ :type version: :py:class:`int`
+
+ :return: None
+ """
+ if not isinstance(version, int):
+ raise TypeError("version must be an integer")
+
+ _lib.X509_set_version(self._x509, version)
+
+
+ def get_version(self):
+ """
+ Return version number of the certificate
+
+ :return: Version number as a Python integer
+ """
+ return _lib.X509_get_version(self._x509)
+
+
+ def get_pubkey(self):
+ """
+ Get the public key of the certificate
+
+ :return: The public key
+ """
+ pkey = PKey.__new__(PKey)
+ pkey._pkey = _lib.X509_get_pubkey(self._x509)
+ if pkey._pkey == _ffi.NULL:
+ _raise_current_error()
+ pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free)
+ pkey._only_public = True
+ return pkey
+
+
+ def set_pubkey(self, pkey):
+ """
+ Set the public key of the certificate
+
+ :param pkey: The public key
+
+ :return: None
+ """
+ if not isinstance(pkey, PKey):
+ raise TypeError("pkey must be a PKey instance")
+
+ set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey)
+ if not set_result:
+ _raise_current_error()
+
+
+ def sign(self, pkey, digest):
+ """
+ Sign the certificate using the supplied key and digest
+
+ :param pkey: The key to sign with
+ :param digest: The message digest to use
+ :return: None
+ """
+ if not isinstance(pkey, PKey):
+ raise TypeError("pkey must be a PKey instance")
+
+ if pkey._only_public:
+ raise ValueError("Key only has public part")
+
+ if not pkey._initialized:
+ raise ValueError("Key is uninitialized")
+
+ evp_md = _lib.EVP_get_digestbyname(_byte_string(digest))
+ if evp_md == _ffi.NULL:
+ raise ValueError("No such digest method")
+
+ sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md)
+ if not sign_result:
+ _raise_current_error()
+
+
+ def get_signature_algorithm(self):
+ """
+ Retrieve the signature algorithm used in the certificate
+
+ :return: A byte string giving the name of the signature algorithm used in
+ the certificate.
+ :raise ValueError: If the signature algorithm is undefined.
+ """
+ alg = self._x509.cert_info.signature.algorithm
+ nid = _lib.OBJ_obj2nid(alg)
+ if nid == _lib.NID_undef:
+ raise ValueError("Undefined signature algorithm")
+ return _ffi.string(_lib.OBJ_nid2ln(nid))
+
+
+ def digest(self, digest_name):
+ """
+ Return the digest of the X509 object.
+
+ :param digest_name: The name of the digest algorithm to use.
+ :type digest_name: :py:class:`bytes`
+
+ :return: The digest of the object
+ """
+ digest = _lib.EVP_get_digestbyname(_byte_string(digest_name))
+ if digest == _ffi.NULL:
+ raise ValueError("No such digest method")
+
+ result_buffer = _ffi.new("char[]", _lib.EVP_MAX_MD_SIZE)
+ result_length = _ffi.new("unsigned int[]", 1)
+ result_length[0] = len(result_buffer)
+
+ digest_result = _lib.X509_digest(
+ self._x509, digest, result_buffer, result_length)
+
+ if not digest_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return b":".join([
+ b16encode(ch).upper() for ch
+ in _ffi.buffer(result_buffer, result_length[0])])
+
+
+ def subject_name_hash(self):
+ """
+ Return the hash of the X509 subject.
+
+ :return: The hash of the subject.
+ """
+ return _lib.X509_subject_name_hash(self._x509)
+
+
+ def set_serial_number(self, serial):
+ """
+ Set serial number of the certificate
+
+ :param serial: The serial number
+ :type serial: :py:class:`int`
+
+ :return: None
+ """
+ if not isinstance(serial, _integer_types):
+ raise TypeError("serial must be an integer")
+
+ hex_serial = hex(serial)[2:]
+ if not isinstance(hex_serial, bytes):
+ hex_serial = hex_serial.encode('ascii')
+
+ bignum_serial = _ffi.new("BIGNUM**")
+
+ # BN_hex2bn stores the result in &bignum. Unless it doesn't feel like
+ # it. If bignum is still NULL after this call, then the return value is
+ # actually the result. I hope. -exarkun
+ small_serial = _lib.BN_hex2bn(bignum_serial, hex_serial)
+
+ if bignum_serial[0] == _ffi.NULL:
+ set_result = _lib.ASN1_INTEGER_set(
+ _lib.X509_get_serialNumber(self._x509), small_serial)
+ if set_result:
+ # TODO Not tested
+ _raise_current_error()
+ else:
+ asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL)
+ _lib.BN_free(bignum_serial[0])
+ if asn1_serial == _ffi.NULL:
+ # TODO Not tested
+ _raise_current_error()
+ asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free)
+ set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial)
+ if not set_result:
+ # TODO Not tested
+ _raise_current_error()
+
+
+ def get_serial_number(self):
+ """
+ Return serial number of the certificate
+
+ :return: Serial number as a Python integer
+ """
+ asn1_serial = _lib.X509_get_serialNumber(self._x509)
+ bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL)
+ try:
+ hex_serial = _lib.BN_bn2hex(bignum_serial)
+ try:
+ hexstring_serial = _ffi.string(hex_serial)
+ serial = int(hexstring_serial, 16)
+ return serial
+ finally:
+ _lib.OPENSSL_free(hex_serial)
+ finally:
+ _lib.BN_free(bignum_serial)
+
+
+ def gmtime_adj_notAfter(self, amount):
+ """
+ Adjust the time stamp for when the certificate stops being valid
+
+ :param amount: The number of seconds by which to adjust the ending
+ validity time.
+ :type amount: :py:class:`int`
+
+ :return: None
+ """
+ if not isinstance(amount, int):
+ raise TypeError("amount must be an integer")
+
+ notAfter = _lib.X509_get_notAfter(self._x509)
+ _lib.X509_gmtime_adj(notAfter, amount)
+
+
+ def gmtime_adj_notBefore(self, amount):
+ """
+ Change the timestamp for when the certificate starts being valid to the current
+ time plus an offset.
+
+ :param amount: The number of seconds by which to adjust the starting validity
+ time.
+ :return: None
+ """
+ if not isinstance(amount, int):
+ raise TypeError("amount must be an integer")
+
+ notBefore = _lib.X509_get_notBefore(self._x509)
+ _lib.X509_gmtime_adj(notBefore, amount)
+
+
+ def has_expired(self):
+ """
+ Check whether the certificate has expired.
+
+ :return: True if the certificate has expired, false otherwise
+ """
+ now = int(time())
+ notAfter = _lib.X509_get_notAfter(self._x509)
+ return _lib.ASN1_UTCTIME_cmp_time_t(
+ _ffi.cast('ASN1_UTCTIME*', notAfter), now) < 0
+
+
+ def _get_boundary_time(self, which):
+ return _get_asn1_time(which(self._x509))
+
+
+ def get_notBefore(self):
+ """
+ Retrieve the time stamp for when the certificate starts being valid
+
+ :return: A string giving the timestamp, in the format::
+
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+
+ or None if there is no value set.
+ """
+ return self._get_boundary_time(_lib.X509_get_notBefore)
+
+
+ def _set_boundary_time(self, which, when):
+ return _set_asn1_time(which(self._x509), when)
+
+
+ def set_notBefore(self, when):
+ """
+ Set the time stamp for when the certificate starts being valid
+
+ :param when: A string giving the timestamp, in the format:
+
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+ :type when: :py:class:`bytes`
+
+ :return: None
+ """
+ return self._set_boundary_time(_lib.X509_get_notBefore, when)
+
+
+ def get_notAfter(self):
+ """
+ Retrieve the time stamp for when the certificate stops being valid
+
+ :return: A string giving the timestamp, in the format::
+
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+
+ or None if there is no value set.
+ """
+ return self._get_boundary_time(_lib.X509_get_notAfter)
+
+
+ def set_notAfter(self, when):
+ """
+ Set the time stamp for when the certificate stops being valid
+
+ :param when: A string giving the timestamp, in the format:
+
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+ :type when: :py:class:`bytes`
+
+ :return: None
+ """
+ return self._set_boundary_time(_lib.X509_get_notAfter, when)
+
+
+ def _get_name(self, which):
+ name = X509Name.__new__(X509Name)
+ name._name = which(self._x509)
+ if name._name == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ # The name is owned by the X509 structure. As long as the X509Name
+ # Python object is alive, keep the X509 Python object alive.
+ name._owner = self
+
+ return name
+
+
+ def _set_name(self, which, name):
+ if not isinstance(name, X509Name):
+ raise TypeError("name must be an X509Name")
+ set_result = which(self._x509, name._name)
+ if not set_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def get_issuer(self):
+ """
+ Create an X509Name object for the issuer of the certificate
+
+ :return: An X509Name object
+ """
+ return self._get_name(_lib.X509_get_issuer_name)
+
+
+ def set_issuer(self, issuer):
+ """
+ Set the issuer of the certificate
+
+ :param issuer: The issuer name
+ :type issuer: :py:class:`X509Name`
+
+ :return: None
+ """
+ return self._set_name(_lib.X509_set_issuer_name, issuer)
+
+
+ def get_subject(self):
+ """
+ Create an X509Name object for the subject of the certificate
+
+ :return: An X509Name object
+ """
+ return self._get_name(_lib.X509_get_subject_name)
+
+
+ def set_subject(self, subject):
+ """
+ Set the subject of the certificate
+
+ :param subject: The subject name
+ :type subject: :py:class:`X509Name`
+ :return: None
+ """
+ return self._set_name(_lib.X509_set_subject_name, subject)
+
+
+ def get_extension_count(self):
+ """
+ Get the number of extensions on the certificate.
+
+ :return: The number of extensions as an integer.
+ """
+ return _lib.X509_get_ext_count(self._x509)
+
+
+ def add_extensions(self, extensions):
+ """
+ Add extensions to the certificate.
+
+ :param extensions: a sequence of X509Extension objects
+ :return: None
+ """
+ for ext in extensions:
+ if not isinstance(ext, X509Extension):
+ raise ValueError("One of the elements is not an X509Extension")
+
+ add_result = _lib.X509_add_ext(self._x509, ext._extension, -1)
+ if not add_result:
+ _raise_current_error()
+
+
+ def get_extension(self, index):
+ """
+ Get a specific extension of the certificate by index.
+
+ :param index: The index of the extension to retrieve.
+ :return: The X509Extension object at the specified index.
+ """
+ ext = X509Extension.__new__(X509Extension)
+ ext._extension = _lib.X509_get_ext(self._x509, index)
+ if ext._extension == _ffi.NULL:
+ raise IndexError("extension index out of bounds")
+
+ extension = _lib.X509_EXTENSION_dup(ext._extension)
+ ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free)
+ return ext
+
+X509Type = X509
+
+
+
+class X509Store(object):
+ def __init__(self):
+ store = _lib.X509_STORE_new()
+ self._store = _ffi.gc(store, _lib.X509_STORE_free)
+
+
+ def add_cert(self, cert):
+ if not isinstance(cert, X509):
+ raise TypeError()
+
+ result = _lib.X509_STORE_add_cert(self._store, cert._x509)
+ if not result:
+ _raise_current_error()
+
+
+X509StoreType = X509Store
+
+
+class X509StoreContextError(Exception):
+ """
+ An error occurred while verifying a certificate using
+ `OpenSSL.X509StoreContext.verify_certificate`.
+
+ :ivar certificate: The certificate which caused verificate failure.
+ :type cert: :class:`X509`
+
+ """
+ def __init__(self, message, certificate):
+ super(X509StoreContextError, self).__init__(message)
+ self.certificate = certificate
+
+
+class X509StoreContext(object):
+ """
+ An X.509 store context.
+
+ An :py:class:`X509StoreContext` is used to define some of the criteria for
+ certificate verification. The information encapsulated in this object
+ includes, but is not limited to, a set of trusted certificates,
+ verification parameters, and revoked certificates.
+
+ Of these, only the set of trusted certificates is currently exposed.
+
+ :ivar _store_ctx: The underlying X509_STORE_CTX structure used by this
+ instance. It is dynamically allocated and automatically garbage
+ collected.
+
+ :ivar _store: See the ``store`` ``__init__`` parameter.
+
+ :ivar _cert: See the ``certificate`` ``__init__`` parameter.
+ """
+
+ def __init__(self, store, certificate):
+ """
+ :param X509Store store: The certificates which will be trusted for the
+ purposes of any verifications.
+
+ :param X509 certificate: The certificate to be verified.
+ """
+ store_ctx = _lib.X509_STORE_CTX_new()
+ self._store_ctx = _ffi.gc(store_ctx, _lib.X509_STORE_CTX_free)
+ self._store = store
+ self._cert = certificate
+ # Make the store context available for use after instantiating this
+ # class by initializing it now. Per testing, subsequent calls to
+ # :py:meth:`_init` have no adverse affect.
+ self._init()
+
+
+ def _init(self):
+ """
+ Set up the store context for a subsequent verification operation.
+ """
+ ret = _lib.X509_STORE_CTX_init(self._store_ctx, self._store._store, self._cert._x509, _ffi.NULL)
+ if ret <= 0:
+ _raise_current_error()
+
+
+ def _cleanup(self):
+ """
+ Internally cleans up the store context.
+
+ The store context can then be reused with a new call to
+ :py:meth:`_init`.
+ """
+ _lib.X509_STORE_CTX_cleanup(self._store_ctx)
+
+
+ def _exception_from_context(self):
+ """
+ Convert an OpenSSL native context error failure into a Python
+ exception.
+
+ When a call to native OpenSSL X509_verify_cert fails, additonal information
+ about the failure can be obtained from the store context.
+ """
+ errors = [
+ _lib.X509_STORE_CTX_get_error(self._store_ctx),
+ _lib.X509_STORE_CTX_get_error_depth(self._store_ctx),
+ _native(_ffi.string(_lib.X509_verify_cert_error_string(
+ _lib.X509_STORE_CTX_get_error(self._store_ctx)))),
+ ]
+ # A context error should always be associated with a certificate, so we
+ # expect this call to never return :class:`None`.
+ _x509 = _lib.X509_STORE_CTX_get_current_cert(self._store_ctx)
+ _cert = _lib.X509_dup(_x509)
+ pycert = X509.__new__(X509)
+ pycert._x509 = _ffi.gc(_cert, _lib.X509_free)
+ return X509StoreContextError(errors, pycert)
+
+
+ def set_store(self, store):
+ """
+ Set the context's trust store.
+
+ :param X509Store store: The certificates which will be trusted for the
+ purposes of any *future* verifications.
+ """
+ self._store = store
+
+
+ def verify_certificate(self):
+ """
+ Verify a certificate in a context.
+
+ :param store_ctx: The :py:class:`X509StoreContext` to verify.
+ :raises: Error
+ """
+ # Always re-initialize the store context in case
+ # :py:meth:`verify_certificate` is called multiple times.
+ self._init()
+ ret = _lib.X509_verify_cert(self._store_ctx)
+ self._cleanup()
+ if ret <= 0:
+ raise self._exception_from_context()
+
+
+
+def load_certificate(type, buffer):
+ """
+ Load a certificate from a buffer
+
+ :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
+
+ :param buffer: The buffer the certificate is stored in
+ :type buffer: :py:class:`bytes`
+
+ :return: The X509 object
+ """
+ if isinstance(buffer, _text_type):
+ buffer = buffer.encode("ascii")
+
+ bio = _new_mem_buf(buffer)
+
+ if type == FILETYPE_PEM:
+ x509 = _lib.PEM_read_bio_X509(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL)
+ elif type == FILETYPE_ASN1:
+ x509 = _lib.d2i_X509_bio(bio, _ffi.NULL);
+ else:
+ raise ValueError(
+ "type argument must be FILETYPE_PEM or FILETYPE_ASN1")
+
+ if x509 == _ffi.NULL:
+ _raise_current_error()
+
+ cert = X509.__new__(X509)
+ cert._x509 = _ffi.gc(x509, _lib.X509_free)
+ return cert
+
+
+def dump_certificate(type, cert):
+ """
+ Dump a certificate to a buffer
+
+ :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or
+ FILETYPE_TEXT)
+ :param cert: The certificate to dump
+ :return: The buffer with the dumped certificate in
+ """
+ bio = _new_mem_buf()
+
+ if type == FILETYPE_PEM:
+ result_code = _lib.PEM_write_bio_X509(bio, cert._x509)
+ elif type == FILETYPE_ASN1:
+ result_code = _lib.i2d_X509_bio(bio, cert._x509)
+ elif type == FILETYPE_TEXT:
+ result_code = _lib.X509_print_ex(bio, cert._x509, 0, 0)
+ else:
+ raise ValueError(
+ "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or "
+ "FILETYPE_TEXT")
+
+ return _bio_to_string(bio)
+
+
+
+def dump_privatekey(type, pkey, cipher=None, passphrase=None):
+ """
+ Dump a private key to a buffer
+
+ :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or
+ FILETYPE_TEXT)
+ :param pkey: The PKey to dump
+ :param cipher: (optional) if encrypted PEM format, the cipher to
+ use
+ :param passphrase: (optional) if encrypted PEM format, this can be either
+ the passphrase to use, or a callback for providing the
+ passphrase.
+ :return: The buffer with the dumped key in
+ :rtype: :py:data:`str`
+ """
+ bio = _new_mem_buf()
+
+ if cipher is not None:
+ if passphrase is None:
+ raise TypeError(
+ "if a value is given for cipher "
+ "one must also be given for passphrase")
+ cipher_obj = _lib.EVP_get_cipherbyname(_byte_string(cipher))
+ if cipher_obj == _ffi.NULL:
+ raise ValueError("Invalid cipher name")
+ else:
+ cipher_obj = _ffi.NULL
+
+ helper = _PassphraseHelper(type, passphrase)
+ if type == FILETYPE_PEM:
+ result_code = _lib.PEM_write_bio_PrivateKey(
+ bio, pkey._pkey, cipher_obj, _ffi.NULL, 0,
+ helper.callback, helper.callback_args)
+ helper.raise_if_problem()
+ elif type == FILETYPE_ASN1:
+ result_code = _lib.i2d_PrivateKey_bio(bio, pkey._pkey)
+ elif type == FILETYPE_TEXT:
+ rsa = _lib.EVP_PKEY_get1_RSA(pkey._pkey)
+ result_code = _lib.RSA_print(bio, rsa, 0)
+ # TODO RSA_free(rsa)?
+ else:
+ raise ValueError(
+ "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or "
+ "FILETYPE_TEXT")
+
+ if result_code == 0:
+ _raise_current_error()
+
+ return _bio_to_string(bio)
+
+
+
+def _X509_REVOKED_dup(original):
+ copy = _lib.X509_REVOKED_new()
+ if copy == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ if original.serialNumber != _ffi.NULL:
+ _lib.ASN1_INTEGER_free(copy.serialNumber)
+ copy.serialNumber = _lib.ASN1_INTEGER_dup(original.serialNumber)
+
+ if original.revocationDate != _ffi.NULL:
+ _lib.ASN1_TIME_free(copy.revocationDate)
+ copy.revocationDate = _lib.M_ASN1_TIME_dup(original.revocationDate)
+
+ if original.extensions != _ffi.NULL:
+ extension_stack = _lib.sk_X509_EXTENSION_new_null()
+ for i in range(_lib.sk_X509_EXTENSION_num(original.extensions)):
+ original_ext = _lib.sk_X509_EXTENSION_value(original.extensions, i)
+ copy_ext = _lib.X509_EXTENSION_dup(original_ext)
+ _lib.sk_X509_EXTENSION_push(extension_stack, copy_ext)
+ copy.extensions = extension_stack
+
+ copy.sequence = original.sequence
+ return copy
+
+
+
+class Revoked(object):
+ # http://www.openssl.org/docs/apps/x509v3_config.html#CRL_distribution_points_
+ # which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches
+ # OCSP_crl_reason_str. We use the latter, just like the command line
+ # program.
+ _crl_reasons = [
+ b"unspecified",
+ b"keyCompromise",
+ b"CACompromise",
+ b"affiliationChanged",
+ b"superseded",
+ b"cessationOfOperation",
+ b"certificateHold",
+ # b"removeFromCRL",
+ ]
+
+ def __init__(self):
+ revoked = _lib.X509_REVOKED_new()
+ self._revoked = _ffi.gc(revoked, _lib.X509_REVOKED_free)
+
+
+ def set_serial(self, hex_str):
+ """
+ Set the serial number of a revoked Revoked structure
+
+ :param hex_str: The new serial number.
+ :type hex_str: :py:data:`str`
+ :return: None
+ """
+ bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free)
+ bignum_ptr = _ffi.new("BIGNUM**")
+ bignum_ptr[0] = bignum_serial
+ bn_result = _lib.BN_hex2bn(bignum_ptr, hex_str)
+ if not bn_result:
+ raise ValueError("bad hex string")
+
+ asn1_serial = _ffi.gc(
+ _lib.BN_to_ASN1_INTEGER(bignum_serial, _ffi.NULL),
+ _lib.ASN1_INTEGER_free)
+ _lib.X509_REVOKED_set_serialNumber(self._revoked, asn1_serial)
+
+
+ def get_serial(self):
+ """
+ Return the serial number of a Revoked structure
+
+ :return: The serial number as a string
+ """
+ bio = _new_mem_buf()
+
+ result = _lib.i2a_ASN1_INTEGER(bio, self._revoked.serialNumber)
+ if result < 0:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return _bio_to_string(bio)
+
+
+ def _delete_reason(self):
+ stack = self._revoked.extensions
+ for i in range(_lib.sk_X509_EXTENSION_num(stack)):
+ ext = _lib.sk_X509_EXTENSION_value(stack, i)
+ if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason:
+ _lib.X509_EXTENSION_free(ext)
+ _lib.sk_X509_EXTENSION_delete(stack, i)
+ break
+
+
+ def set_reason(self, reason):
+ """
+ Set the reason of a Revoked object.
+
+ If :py:data:`reason` is :py:data:`None`, delete the reason instead.
+
+ :param reason: The reason string.
+ :type reason: :py:class:`str` or :py:class:`NoneType`
+ :return: None
+ """
+ if reason is None:
+ self._delete_reason()
+ elif not isinstance(reason, bytes):
+ raise TypeError("reason must be None or a byte string")
+ else:
+ reason = reason.lower().replace(b' ', b'')
+ reason_code = [r.lower() for r in self._crl_reasons].index(reason)
+
+ new_reason_ext = _lib.ASN1_ENUMERATED_new()
+ if new_reason_ext == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ new_reason_ext = _ffi.gc(new_reason_ext, _lib.ASN1_ENUMERATED_free)
+
+ set_result = _lib.ASN1_ENUMERATED_set(new_reason_ext, reason_code)
+ if set_result == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ self._delete_reason()
+ add_result = _lib.X509_REVOKED_add1_ext_i2d(
+ self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0)
+
+ if not add_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def get_reason(self):
+ """
+ Return the reason of a Revoked object.
+
+ :return: The reason as a string
+ """
+ extensions = self._revoked.extensions
+ for i in range(_lib.sk_X509_EXTENSION_num(extensions)):
+ ext = _lib.sk_X509_EXTENSION_value(extensions, i)
+ if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason:
+ bio = _new_mem_buf()
+
+ print_result = _lib.X509V3_EXT_print(bio, ext, 0, 0)
+ if not print_result:
+ print_result = _lib.M_ASN1_OCTET_STRING_print(bio, ext.value)
+ if print_result == 0:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return _bio_to_string(bio)
+
+
+ def all_reasons(self):
+ """
+ Return a list of all the supported reason strings.
+
+ :return: A list of reason strings.
+ """
+ return self._crl_reasons[:]
+
+
+ def set_rev_date(self, when):
+ """
+ Set the revocation timestamp
+
+ :param when: A string giving the timestamp, in the format:
+
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+
+ :return: None
+ """
+ return _set_asn1_time(self._revoked.revocationDate, when)
+
+
+ def get_rev_date(self):
+ """
+ Retrieve the revocation date
+
+ :return: A string giving the timestamp, in the format:
+
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+ """
+ return _get_asn1_time(self._revoked.revocationDate)
+
+
+
+class CRL(object):
+ def __init__(self):
+ """
+ Create a new empty CRL object.
+ """
+ crl = _lib.X509_CRL_new()
+ self._crl = _ffi.gc(crl, _lib.X509_CRL_free)
+
+
+ def get_revoked(self):
+ """
+ Return revoked portion of the CRL structure (by value not reference).
+
+ :return: A tuple of Revoked objects.
+ """
+ results = []
+ revoked_stack = self._crl.crl.revoked
+ for i in range(_lib.sk_X509_REVOKED_num(revoked_stack)):
+ revoked = _lib.sk_X509_REVOKED_value(revoked_stack, i)
+ revoked_copy = _X509_REVOKED_dup(revoked)
+ pyrev = Revoked.__new__(Revoked)
+ pyrev._revoked = _ffi.gc(revoked_copy, _lib.X509_REVOKED_free)
+ results.append(pyrev)
+ if results:
+ return tuple(results)
+
+
+ def add_revoked(self, revoked):
+ """
+ Add a revoked (by value not reference) to the CRL structure
+
+ :param revoked: The new revoked.
+ :type revoked: :class:`X509`
+
+ :return: None
+ """
+ copy = _X509_REVOKED_dup(revoked._revoked)
+ if copy == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ add_result = _lib.X509_CRL_add0_revoked(self._crl, copy)
+ if add_result == 0:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def export(self, cert, key, type=FILETYPE_PEM, days=100,
+ digest=_UNSPECIFIED):
+ """
+ export a CRL as a string
+
+ :param cert: Used to sign CRL.
+ :type cert: :class:`X509`
+
+ :param key: Used to sign CRL.
+ :type key: :class:`PKey`
+
+ :param type: The export format, either :py:data:`FILETYPE_PEM`,
+ :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`.
+
+ :param int days: The number of days until the next update of this CRL.
+
+ :param bytes digest: The name of the message digest to use (eg
+ ``b"sha1"``).
+
+ :return: :py:data:`bytes`
+ """
+ if not isinstance(cert, X509):
+ raise TypeError("cert must be an X509 instance")
+ if not isinstance(key, PKey):
+ raise TypeError("key must be a PKey instance")
+ if not isinstance(type, int):
+ raise TypeError("type must be an integer")
+
+ if digest is _UNSPECIFIED:
+ _warn(
+ "The default message digest (md5) is deprecated. "
+ "Pass the name of a message digest explicitly.",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+ digest = b"md5"
+
+ digest_obj = _lib.EVP_get_digestbyname(digest)
+ if digest_obj == _ffi.NULL:
+ raise ValueError("No such digest method")
+
+ bio = _lib.BIO_new(_lib.BIO_s_mem())
+ if bio == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ # A scratch time object to give different values to different CRL fields
+ sometime = _lib.ASN1_TIME_new()
+ if sometime == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ _lib.X509_gmtime_adj(sometime, 0)
+ _lib.X509_CRL_set_lastUpdate(self._crl, sometime)
+
+ _lib.X509_gmtime_adj(sometime, days * 24 * 60 * 60)
+ _lib.X509_CRL_set_nextUpdate(self._crl, sometime)
+
+ _lib.X509_CRL_set_issuer_name(self._crl, _lib.X509_get_subject_name(cert._x509))
+
+ sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, digest_obj)
+ if not sign_result:
+ _raise_current_error()
+
+ if type == FILETYPE_PEM:
+ ret = _lib.PEM_write_bio_X509_CRL(bio, self._crl)
+ elif type == FILETYPE_ASN1:
+ ret = _lib.i2d_X509_CRL_bio(bio, self._crl)
+ elif type == FILETYPE_TEXT:
+ ret = _lib.X509_CRL_print(bio, self._crl)
+ else:
+ raise ValueError(
+ "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT")
+
+ if not ret:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return _bio_to_string(bio)
+CRLType = CRL
+
+
+
+class PKCS7(object):
+ def type_is_signed(self):
+ """
+ Check if this NID_pkcs7_signed object
+
+ :return: True if the PKCS7 is of type signed
+ """
+ if _lib.PKCS7_type_is_signed(self._pkcs7):
+ return True
+ return False
+
+
+ def type_is_enveloped(self):
+ """
+ Check if this NID_pkcs7_enveloped object
+
+ :returns: True if the PKCS7 is of type enveloped
+ """
+ if _lib.PKCS7_type_is_enveloped(self._pkcs7):
+ return True
+ return False
+
+
+ def type_is_signedAndEnveloped(self):
+ """
+ Check if this NID_pkcs7_signedAndEnveloped object
+
+ :returns: True if the PKCS7 is of type signedAndEnveloped
+ """
+ if _lib.PKCS7_type_is_signedAndEnveloped(self._pkcs7):
+ return True
+ return False
+
+
+ def type_is_data(self):
+ """
+ Check if this NID_pkcs7_data object
+
+ :return: True if the PKCS7 is of type data
+ """
+ if _lib.PKCS7_type_is_data(self._pkcs7):
+ return True
+ return False
+
+
+ def get_type_name(self):
+ """
+ Returns the type name of the PKCS7 structure
+
+ :return: A string with the typename
+ """
+ nid = _lib.OBJ_obj2nid(self._pkcs7.type)
+ string_type = _lib.OBJ_nid2sn(nid)
+ return _ffi.string(string_type)
+
+PKCS7Type = PKCS7
+
+
+
+class PKCS12(object):
+ def __init__(self):
+ self._pkey = None
+ self._cert = None
+ self._cacerts = None
+ self._friendlyname = None
+
+
+ def get_certificate(self):
+ """
+ Return certificate portion of the PKCS12 structure
+
+ :return: X509 object containing the certificate
+ """
+ return self._cert
+
+
+ def set_certificate(self, cert):
+ """
+ Replace the certificate portion of the PKCS12 structure
+
+ :param cert: The new certificate.
+ :type cert: :py:class:`X509` or :py:data:`None`
+ :return: None
+ """
+ if not isinstance(cert, X509):
+ raise TypeError("cert must be an X509 instance")
+ self._cert = cert
+
+
+ def get_privatekey(self):
+ """
+ Return private key portion of the PKCS12 structure
+
+ :returns: PKey object containing the private key
+ """
+ return self._pkey
+
+
+ def set_privatekey(self, pkey):
+ """
+ Replace or set the certificate portion of the PKCS12 structure
+
+ :param pkey: The new private key.
+ :type pkey: :py:class:`PKey`
+ :return: None
+ """
+ if not isinstance(pkey, PKey):
+ raise TypeError("pkey must be a PKey instance")
+ self._pkey = pkey
+
+
+ def get_ca_certificates(self):
+ """
+ Return CA certificates within of the PKCS12 object
+
+ :return: A newly created tuple containing the CA certificates in the chain,
+ if any are present, or None if no CA certificates are present.
+ """
+ if self._cacerts is not None:
+ return tuple(self._cacerts)
+
+
+ def set_ca_certificates(self, cacerts):
+ """
+ Replace or set the CA certificates within the PKCS12 object.
+
+ :param cacerts: The new CA certificates.
+ :type cacerts: :py:data:`None` or an iterable of :py:class:`X509`
+ :return: None
+ """
+ if cacerts is None:
+ self._cacerts = None
+ else:
+ cacerts = list(cacerts)
+ for cert in cacerts:
+ if not isinstance(cert, X509):
+ raise TypeError("iterable must only contain X509 instances")
+ self._cacerts = cacerts
+
+
+ def set_friendlyname(self, name):
+ """
+ Replace or set the certificate portion of the PKCS12 structure
+
+ :param name: The new friendly name.
+ :type name: :py:class:`bytes`
+ :return: None
+ """
+ if name is None:
+ self._friendlyname = None
+ elif not isinstance(name, bytes):
+ raise TypeError("name must be a byte string or None (not %r)" % (name,))
+ self._friendlyname = name
+
+
+ def get_friendlyname(self):
+ """
+ Return friendly name portion of the PKCS12 structure
+
+ :returns: String containing the friendlyname
+ """
+ return self._friendlyname
+
+
+ def export(self, passphrase=None, iter=2048, maciter=1):
+ """
+ Dump a PKCS12 object as a string. See also "man PKCS12_create".
+
+ :param passphrase: used to encrypt the PKCS12
+ :type passphrase: :py:data:`bytes`
+
+ :param iter: How many times to repeat the encryption
+ :type iter: :py:data:`int`
+
+ :param maciter: How many times to repeat the MAC
+ :type maciter: :py:data:`int`
+
+ :return: The string containing the PKCS12
+ """
+ passphrase = _text_to_bytes_and_warn("passphrase", passphrase)
+
+ if self._cacerts is None:
+ cacerts = _ffi.NULL
+ else:
+ cacerts = _lib.sk_X509_new_null()
+ cacerts = _ffi.gc(cacerts, _lib.sk_X509_free)
+ for cert in self._cacerts:
+ _lib.sk_X509_push(cacerts, cert._x509)
+
+ if passphrase is None:
+ passphrase = _ffi.NULL
+
+ friendlyname = self._friendlyname
+ if friendlyname is None:
+ friendlyname = _ffi.NULL
+
+ if self._pkey is None:
+ pkey = _ffi.NULL
+ else:
+ pkey = self._pkey._pkey
+
+ if self._cert is None:
+ cert = _ffi.NULL
+ else:
+ cert = self._cert._x509
+
+ pkcs12 = _lib.PKCS12_create(
+ passphrase, friendlyname, pkey, cert, cacerts,
+ _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
+ _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
+ iter, maciter, 0)
+ if pkcs12 == _ffi.NULL:
+ _raise_current_error()
+ pkcs12 = _ffi.gc(pkcs12, _lib.PKCS12_free)
+
+ bio = _new_mem_buf()
+ _lib.i2d_PKCS12_bio(bio, pkcs12)
+ return _bio_to_string(bio)
+
+PKCS12Type = PKCS12
+
+
+
+class NetscapeSPKI(object):
+ def __init__(self):
+ spki = _lib.NETSCAPE_SPKI_new()
+ self._spki = _ffi.gc(spki, _lib.NETSCAPE_SPKI_free)
+
+
+ def sign(self, pkey, digest):
+ """
+ Sign the certificate request using the supplied key and digest
+
+ :param pkey: The key to sign with
+ :param digest: The message digest to use
+ :return: None
+ """
+ if pkey._only_public:
+ raise ValueError("Key has only public part")
+
+ if not pkey._initialized:
+ raise ValueError("Key is uninitialized")
+
+ digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest))
+ if digest_obj == _ffi.NULL:
+ raise ValueError("No such digest method")
+
+ sign_result = _lib.NETSCAPE_SPKI_sign(self._spki, pkey._pkey, digest_obj)
+ if not sign_result:
+ # TODO: This is untested.
+ _raise_current_error()
+
+
+ def verify(self, key):
+ """
+ Verifies a certificate request using the supplied public key
+
+ :param key: a public key
+ :return: True if the signature is correct.
+ :raise OpenSSL.crypto.Error: If the signature is invalid or there is a
+ problem verifying the signature.
+ """
+ answer = _lib.NETSCAPE_SPKI_verify(self._spki, key._pkey)
+ if answer <= 0:
+ _raise_current_error()
+ return True
+
+
+ def b64_encode(self):
+ """
+ Generate a base64 encoded string from an SPKI
+
+ :return: The base64 encoded string
+ """
+ encoded = _lib.NETSCAPE_SPKI_b64_encode(self._spki)
+ result = _ffi.string(encoded)
+ _lib.CRYPTO_free(encoded)
+ return result
+
+
+ def get_pubkey(self):
+ """
+ Get the public key of the certificate
+
+ :return: The public key
+ """
+ pkey = PKey.__new__(PKey)
+ pkey._pkey = _lib.NETSCAPE_SPKI_get_pubkey(self._spki)
+ if pkey._pkey == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free)
+ pkey._only_public = True
+ return pkey
+
+
+ def set_pubkey(self, pkey):
+ """
+ Set the public key of the certificate
+
+ :param pkey: The public key
+ :return: None
+ """
+ set_result = _lib.NETSCAPE_SPKI_set_pubkey(self._spki, pkey._pkey)
+ if not set_result:
+ # TODO: This is untested.
+ _raise_current_error()
+NetscapeSPKIType = NetscapeSPKI
+
+
+class _PassphraseHelper(object):
+ def __init__(self, type, passphrase, more_args=False, truncate=False):
+ if type != FILETYPE_PEM and passphrase is not None:
+ raise ValueError("only FILETYPE_PEM key format supports encryption")
+ self._passphrase = passphrase
+ self._more_args = more_args
+ self._truncate = truncate
+ self._problems = []
+
+
+ @property
+ def callback(self):
+ if self._passphrase is None:
+ return _ffi.NULL
+ elif isinstance(self._passphrase, bytes):
+ return _ffi.NULL
+ elif callable(self._passphrase):
+ return _ffi.callback("pem_password_cb", self._read_passphrase)
+ else:
+ raise TypeError("Last argument must be string or callable")
+
+
+ @property
+ def callback_args(self):
+ if self._passphrase is None:
+ return _ffi.NULL
+ elif isinstance(self._passphrase, bytes):
+ return self._passphrase
+ elif callable(self._passphrase):
+ return _ffi.NULL
+ else:
+ raise TypeError("Last argument must be string or callable")
+
+
+ def raise_if_problem(self, exceptionType=Error):
+ try:
+ _exception_from_error_queue(exceptionType)
+ except exceptionType as e:
+ from_queue = e
+ if self._problems:
+ raise self._problems[0]
+ return from_queue
+
+
+ def _read_passphrase(self, buf, size, rwflag, userdata):
+ try:
+ if self._more_args:
+ result = self._passphrase(size, rwflag, userdata)
+ else:
+ result = self._passphrase(rwflag)
+ if not isinstance(result, bytes):
+ raise ValueError("String expected")
+ if len(result) > size:
+ if self._truncate:
+ result = result[:size]
+ else:
+ raise ValueError("passphrase returned by callback is too long")
+ for i in range(len(result)):
+ buf[i] = result[i:i + 1]
+ return len(result)
+ except Exception as e:
+ self._problems.append(e)
+ return 0
+
+
+
+def load_privatekey(type, buffer, passphrase=None):
+ """
+ Load a private key from a buffer
+
+ :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
+ :param buffer: The buffer the key is stored in
+ :param passphrase: (optional) if encrypted PEM format, this can be
+ either the passphrase to use, or a callback for
+ providing the passphrase.
+
+ :return: The PKey object
+ """
+ if isinstance(buffer, _text_type):
+ buffer = buffer.encode("ascii")
+
+ bio = _new_mem_buf(buffer)
+
+ helper = _PassphraseHelper(type, passphrase)
+ if type == FILETYPE_PEM:
+ evp_pkey = _lib.PEM_read_bio_PrivateKey(
+ bio, _ffi.NULL, helper.callback, helper.callback_args)
+ helper.raise_if_problem()
+ elif type == FILETYPE_ASN1:
+ evp_pkey = _lib.d2i_PrivateKey_bio(bio, _ffi.NULL)
+ else:
+ raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1")
+
+ if evp_pkey == _ffi.NULL:
+ _raise_current_error()
+
+ pkey = PKey.__new__(PKey)
+ pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free)
+ return pkey
+
+
+
+def dump_certificate_request(type, req):
+ """
+ Dump a certificate request to a buffer
+
+ :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
+ :param req: The certificate request to dump
+ :return: The buffer with the dumped certificate request in
+ """
+ bio = _new_mem_buf()
+
+ if type == FILETYPE_PEM:
+ result_code = _lib.PEM_write_bio_X509_REQ(bio, req._req)
+ elif type == FILETYPE_ASN1:
+ result_code = _lib.i2d_X509_REQ_bio(bio, req._req)
+ elif type == FILETYPE_TEXT:
+ result_code = _lib.X509_REQ_print_ex(bio, req._req, 0, 0)
+ else:
+ raise ValueError("type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT")
+
+ if result_code == 0:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return _bio_to_string(bio)
+
+
+
+def load_certificate_request(type, buffer):
+ """
+ Load a certificate request from a buffer
+
+ :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
+ :param buffer: The buffer the certificate request is stored in
+ :return: The X509Req object
+ """
+ if isinstance(buffer, _text_type):
+ buffer = buffer.encode("ascii")
+
+ bio = _new_mem_buf(buffer)
+
+ if type == FILETYPE_PEM:
+ req = _lib.PEM_read_bio_X509_REQ(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL)
+ elif type == FILETYPE_ASN1:
+ req = _lib.d2i_X509_REQ_bio(bio, _ffi.NULL)
+ else:
+ raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1")
+
+ if req == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ x509req = X509Req.__new__(X509Req)
+ x509req._req = _ffi.gc(req, _lib.X509_REQ_free)
+ return x509req
+
+
+
+def sign(pkey, data, digest):
+ """
+ Sign data with a digest
+
+ :param pkey: Pkey to sign with
+ :param data: data to be signed
+ :param digest: message digest to use
+ :return: signature
+ """
+ data = _text_to_bytes_and_warn("data", data)
+
+ digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest))
+ if digest_obj == _ffi.NULL:
+ raise ValueError("No such digest method")
+
+ md_ctx = _ffi.new("EVP_MD_CTX*")
+ md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup)
+
+ _lib.EVP_SignInit(md_ctx, digest_obj)
+ _lib.EVP_SignUpdate(md_ctx, data, len(data))
+
+ signature_buffer = _ffi.new("unsigned char[]", 512)
+ signature_length = _ffi.new("unsigned int*")
+ signature_length[0] = len(signature_buffer)
+ final_result = _lib.EVP_SignFinal(
+ md_ctx, signature_buffer, signature_length, pkey._pkey)
+
+ if final_result != 1:
+ # TODO: This is untested.
+ _raise_current_error()
+
+ return _ffi.buffer(signature_buffer, signature_length[0])[:]
+
+
+
+def verify(cert, signature, data, digest):
+ """
+ Verify a signature
+
+ :param cert: signing certificate (X509 object)
+ :param signature: signature returned by sign function
+ :param data: data to be verified
+ :param digest: message digest to use
+ :return: None if the signature is correct, raise exception otherwise
+ """
+ data = _text_to_bytes_and_warn("data", data)
+
+ digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest))
+ if digest_obj == _ffi.NULL:
+ raise ValueError("No such digest method")
+
+ pkey = _lib.X509_get_pubkey(cert._x509)
+ if pkey == _ffi.NULL:
+ # TODO: This is untested.
+ _raise_current_error()
+ pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free)
+
+ md_ctx = _ffi.new("EVP_MD_CTX*")
+ md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup)
+
+ _lib.EVP_VerifyInit(md_ctx, digest_obj)
+ _lib.EVP_VerifyUpdate(md_ctx, data, len(data))
+ verify_result = _lib.EVP_VerifyFinal(md_ctx, signature, len(signature), pkey)
+
+ if verify_result != 1:
+ _raise_current_error()
+
+
+def load_crl(type, buffer):
+ """
+ Load a certificate revocation list from a buffer
+
+ :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
+ :param buffer: The buffer the CRL is stored in
+
+ :return: The PKey object
+ """
+ if isinstance(buffer, _text_type):
+ buffer = buffer.encode("ascii")
+
+ bio = _new_mem_buf(buffer)
+
+ if type == FILETYPE_PEM:
+ crl = _lib.PEM_read_bio_X509_CRL(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL)
+ elif type == FILETYPE_ASN1:
+ crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL)
+ else:
+ raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1")
+
+ if crl == _ffi.NULL:
+ _raise_current_error()
+
+ result = CRL.__new__(CRL)
+ result._crl = crl
+ return result
+
+
+
+def load_pkcs7_data(type, buffer):
+ """
+ Load pkcs7 data from a buffer
+
+ :param type: The file type (one of FILETYPE_PEM or FILETYPE_ASN1)
+ :param buffer: The buffer with the pkcs7 data.
+ :return: The PKCS7 object
+ """
+ if isinstance(buffer, _text_type):
+ buffer = buffer.encode("ascii")
+
+ bio = _new_mem_buf(buffer)
+
+ if type == FILETYPE_PEM:
+ pkcs7 = _lib.PEM_read_bio_PKCS7(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL)
+ elif type == FILETYPE_ASN1:
+ pkcs7 = _lib.d2i_PKCS7_bio(bio, _ffi.NULL)
+ else:
+ # TODO: This is untested.
+ _raise_current_error()
+ raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1")
+
+ if pkcs7 == _ffi.NULL:
+ _raise_current_error()
+
+ pypkcs7 = PKCS7.__new__(PKCS7)
+ pypkcs7._pkcs7 = _ffi.gc(pkcs7, _lib.PKCS7_free)
+ return pypkcs7
+
+
+
+def load_pkcs12(buffer, passphrase=None):
+ """
+ Load a PKCS12 object from a buffer
+
+ :param buffer: The buffer the certificate is stored in
+ :param passphrase: (Optional) The password to decrypt the PKCS12 lump
+ :returns: The PKCS12 object
+ """
+ passphrase = _text_to_bytes_and_warn("passphrase", passphrase)
+
+ if isinstance(buffer, _text_type):
+ buffer = buffer.encode("ascii")
+
+ bio = _new_mem_buf(buffer)
+
+ # Use null passphrase if passphrase is None or empty string. With PKCS#12
+ # password based encryption no password and a zero length password are two
+ # different things, but OpenSSL implementation will try both to figure out
+ # which one works.
+ if not passphrase:
+ passphrase = _ffi.NULL
+
+ p12 = _lib.d2i_PKCS12_bio(bio, _ffi.NULL)
+ if p12 == _ffi.NULL:
+ _raise_current_error()
+ p12 = _ffi.gc(p12, _lib.PKCS12_free)
+
+ pkey = _ffi.new("EVP_PKEY**")
+ cert = _ffi.new("X509**")
+ cacerts = _ffi.new("Cryptography_STACK_OF_X509**")
+
+ parse_result = _lib.PKCS12_parse(p12, passphrase, pkey, cert, cacerts)
+ if not parse_result:
+ _raise_current_error()
+
+ cacerts = _ffi.gc(cacerts[0], _lib.sk_X509_free)
+
+ # openssl 1.0.0 sometimes leaves an X509_check_private_key error in the
+ # queue for no particular reason. This error isn't interesting to anyone
+ # outside this function. It's not even interesting to us. Get rid of it.
+ try:
+ _raise_current_error()
+ except Error:
+ pass
+
+ if pkey[0] == _ffi.NULL:
+ pykey = None
+ else:
+ pykey = PKey.__new__(PKey)
+ pykey._pkey = _ffi.gc(pkey[0], _lib.EVP_PKEY_free)
+
+ if cert[0] == _ffi.NULL:
+ pycert = None
+ friendlyname = None
+ else:
+ pycert = X509.__new__(X509)
+ pycert._x509 = _ffi.gc(cert[0], _lib.X509_free)
+
+ friendlyname_length = _ffi.new("int*")
+ friendlyname_buffer = _lib.X509_alias_get0(cert[0], friendlyname_length)
+ friendlyname = _ffi.buffer(friendlyname_buffer, friendlyname_length[0])[:]
+ if friendlyname_buffer == _ffi.NULL:
+ friendlyname = None
+
+ pycacerts = []
+ for i in range(_lib.sk_X509_num(cacerts)):
+ pycacert = X509.__new__(X509)
+ pycacert._x509 = _lib.sk_X509_value(cacerts, i)
+ pycacerts.append(pycacert)
+ if not pycacerts:
+ pycacerts = None
+
+ pkcs12 = PKCS12.__new__(PKCS12)
+ pkcs12._pkey = pykey
+ pkcs12._cert = pycert
+ pkcs12._cacerts = pycacerts
+ pkcs12._friendlyname = friendlyname
+ return pkcs12
+
+
+def _initialize_openssl_threads(get_ident, Lock):
+ import _ssl
+ return
+
+ locks = list(Lock() for n in range(_lib.CRYPTO_num_locks()))
+
+ def locking_function(mode, index, filename, line):
+ if mode & _lib.CRYPTO_LOCK:
+ locks[index].acquire()
+ else:
+ locks[index].release()
+
+ _lib.CRYPTO_set_id_callback(
+ _ffi.callback("unsigned long (*)(void)", get_ident))
+
+ _lib.CRYPTO_set_locking_callback(
+ _ffi.callback(
+ "void (*)(int, int, const char*, int)", locking_function))
+
+
+try:
+ from thread import get_ident
+ from threading import Lock
+except ImportError:
+ pass
+else:
+ _initialize_openssl_threads(get_ident, Lock)
+ del get_ident, Lock
+
+# There are no direct unit tests for this initialization. It is tested
+# indirectly since it is necessary for functions like dump_privatekey when
+# using encryption.
+#
+# Thus OpenSSL.test.test_crypto.FunctionTests.test_dump_privatekey_passphrase
+# and some other similar tests may fail without this (though they may not if
+# the Python runtime has already done some initialization of the underlying
+# OpenSSL library (and is linked against the same one that cryptography is
+# using)).
+_lib.OpenSSL_add_all_algorithms()
+
+# This is similar but exercised mainly by exception_from_error_queue. It calls
+# both ERR_load_crypto_strings() and ERR_load_SSL_strings().
+_lib.SSL_load_error_strings()
diff --git a/lib/Python/Lib/OpenSSL/rand.py b/lib/Python/Lib/OpenSSL/rand.py
new file mode 100644
index 000000000..3adf69369
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/rand.py
@@ -0,0 +1,180 @@
+"""
+PRNG management routines, thin wrappers.
+
+See the file RATIONALE for a short explanation of why this module was written.
+"""
+
+from functools import partial
+
+from six import integer_types as _integer_types
+
+from OpenSSL._util import (
+ ffi as _ffi,
+ lib as _lib,
+ exception_from_error_queue as _exception_from_error_queue,
+ path_string as _path_string)
+
+
+class Error(Exception):
+ """
+ An error occurred in an `OpenSSL.rand` API.
+ """
+
+_raise_current_error = partial(_exception_from_error_queue, Error)
+
+_unspecified = object()
+
+_builtin_bytes = bytes
+
+def bytes(num_bytes):
+ """
+ Get some random bytes as a string.
+
+ :param num_bytes: The number of bytes to fetch
+ :return: A string of random bytes
+ """
+ if not isinstance(num_bytes, _integer_types):
+ raise TypeError("num_bytes must be an integer")
+
+ if num_bytes < 0:
+ raise ValueError("num_bytes must not be negative")
+
+ result_buffer = _ffi.new("char[]", num_bytes)
+ result_code = _lib.RAND_bytes(result_buffer, num_bytes)
+ if result_code == -1:
+ # TODO: No tests for this code path. Triggering a RAND_bytes failure
+ # might involve supplying a custom ENGINE? That's hard.
+ _raise_current_error()
+
+ return _ffi.buffer(result_buffer)[:]
+
+
+
+def add(buffer, entropy):
+ """
+ Add data with a given entropy to the PRNG
+
+ :param buffer: Buffer with random data
+ :param entropy: The entropy (in bytes) measurement of the buffer
+ :return: None
+ """
+ if not isinstance(buffer, _builtin_bytes):
+ raise TypeError("buffer must be a byte string")
+
+ if not isinstance(entropy, int):
+ raise TypeError("entropy must be an integer")
+
+ # TODO Nothing tests this call actually being made, or made properly.
+ _lib.RAND_add(buffer, len(buffer), entropy)
+
+
+
+def seed(buffer):
+ """
+ Alias for rand_add, with entropy equal to length
+
+ :param buffer: Buffer with random data
+ :return: None
+ """
+ if not isinstance(buffer, _builtin_bytes):
+ raise TypeError("buffer must be a byte string")
+
+ # TODO Nothing tests this call actually being made, or made properly.
+ _lib.RAND_seed(buffer, len(buffer))
+
+
+
+def status():
+ """
+ Retrieve the status of the PRNG
+
+ :return: True if the PRNG is seeded enough, false otherwise
+ """
+ return _lib.RAND_status()
+
+
+
+def egd(path, bytes=_unspecified):
+ """
+ Query an entropy gathering daemon (EGD) for random data and add it to the
+ PRNG. I haven't found any problems when the socket is missing, the function
+ just returns 0.
+
+ :param path: The path to the EGD socket
+ :param bytes: (optional) The number of bytes to read, default is 255
+ :returns: The number of bytes read (NB: a value of 0 isn't necessarily an
+ error, check rand.status())
+ """
+ if not isinstance(path, _builtin_bytes):
+ raise TypeError("path must be a byte string")
+
+ if bytes is _unspecified:
+ bytes = 255
+ elif not isinstance(bytes, int):
+ raise TypeError("bytes must be an integer")
+
+ return _lib.RAND_egd_bytes(path, bytes)
+
+
+
+def cleanup():
+ """
+ Erase the memory used by the PRNG.
+
+ :return: None
+ """
+ # TODO Nothing tests this call actually being made, or made properly.
+ _lib.RAND_cleanup()
+
+
+
+def load_file(filename, maxbytes=_unspecified):
+ """
+ Seed the PRNG with data from a file
+
+ :param filename: The file to read data from (``bytes`` or ``unicode``).
+ :param maxbytes: (optional) The number of bytes to read, default is to read
+ the entire file
+
+ :return: The number of bytes read
+ """
+ filename = _path_string(filename)
+
+ if maxbytes is _unspecified:
+ maxbytes = -1
+ elif not isinstance(maxbytes, int):
+ raise TypeError("maxbytes must be an integer")
+
+ return _lib.RAND_load_file(filename, maxbytes)
+
+
+
+def write_file(filename):
+ """
+ Save PRNG state to a file
+
+ :param filename: The file to write data to (``bytes`` or ``unicode``).
+
+ :return: The number of bytes written
+ """
+ filename = _path_string(filename)
+ return _lib.RAND_write_file(filename)
+
+
+# TODO There are no tests for screen at all
+def screen():
+ """
+ Add the current contents of the screen to the PRNG state. Availability:
+ Windows.
+
+ :return: None
+ """
+ _lib.RAND_screen()
+
+if getattr(_lib, 'RAND_screen', None) is None:
+ del screen
+
+
+# TODO There are no tests for the RAND strings being loaded, whatever that
+# means.
+_lib.ERR_load_RAND_strings()
diff --git a/lib/Python/Lib/OpenSSL/test/README b/lib/Python/Lib/OpenSSL/test/README
new file mode 100644
index 000000000..8b3b3ffa6
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/README
@@ -0,0 +1,8 @@
+These tests are meant to be run using twisted's "trial" command.
+
+See http://twistedmatrix.com/trac/wiki/TwistedTrial
+
+For example...
+
+$ sudo python ./setup install
+$ trial OpenSSL
diff --git a/lib/Python/Lib/OpenSSL/test/__init__.py b/lib/Python/Lib/OpenSSL/test/__init__.py
new file mode 100644
index 000000000..9b08060ca
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (C) Jean-Paul Calderone
+# See LICENSE for details.
+
+"""
+Package containing unit tests for :py:mod:`OpenSSL`.
+"""
diff --git a/lib/Python/Lib/OpenSSL/test/test_crypto.py b/lib/Python/Lib/OpenSSL/test/test_crypto.py
new file mode 100644
index 000000000..f6f075154
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/test_crypto.py
@@ -0,0 +1,3671 @@
+# Copyright (c) Jean-Paul Calderone
+# See LICENSE file for details.
+
+"""
+Unit tests for :py:mod:`OpenSSL.crypto`.
+"""
+
+from unittest import main
+from warnings import catch_warnings, simplefilter
+
+import base64
+import os
+import re
+from subprocess import PIPE, Popen
+from datetime import datetime, timedelta
+
+from six import u, b, binary_type, PY3
+from warnings import simplefilter
+from warnings import catch_warnings
+
+from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType
+from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType
+from OpenSSL.crypto import X509Store, X509StoreType, X509StoreContext, X509StoreContextError
+from OpenSSL.crypto import X509Req, X509ReqType
+from OpenSSL.crypto import X509Extension, X509ExtensionType
+from OpenSSL.crypto import load_certificate, load_privatekey
+from OpenSSL.crypto import FILETYPE_PEM, FILETYPE_ASN1, FILETYPE_TEXT
+from OpenSSL.crypto import dump_certificate, load_certificate_request
+from OpenSSL.crypto import dump_certificate_request, dump_privatekey
+from OpenSSL.crypto import PKCS7Type, load_pkcs7_data
+from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12
+from OpenSSL.crypto import CRL, Revoked, load_crl
+from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType
+from OpenSSL.crypto import (
+ sign, verify, get_elliptic_curve, get_elliptic_curves)
+from OpenSSL.test.util import (
+ EqualityTestsMixin, TestCase, WARNING_TYPE_EXPECTED
+)
+from OpenSSL._util import native, lib
+
+def normalize_certificate_pem(pem):
+ return dump_certificate(FILETYPE_PEM, load_certificate(FILETYPE_PEM, pem))
+
+
+def normalize_privatekey_pem(pem):
+ return dump_privatekey(FILETYPE_PEM, load_privatekey(FILETYPE_PEM, pem))
+
+
+GOOD_CIPHER = "blowfish"
+BAD_CIPHER = "zippers"
+
+GOOD_DIGEST = "MD5"
+BAD_DIGEST = "monkeys"
+
+root_cert_pem = b("""-----BEGIN CERTIFICATE-----
+MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU
+ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2
+NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM
+MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U
+ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL
+urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy
+2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF
+1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE
+FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn
+VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE
+BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS
+b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB
+AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi
+hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY
+w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn
+-----END CERTIFICATE-----
+""")
+
+root_key_pem = b("""-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA
+jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU
+3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB
+AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB
+yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa
+6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM
+BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD
+u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk
+PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr
+I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8
+ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3
+6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2
+cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq
+-----END RSA PRIVATE KEY-----
+""")
+
+intermediate_cert_pem = b("""-----BEGIN CERTIFICATE-----
+MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw
+WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw
+DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw
+ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh
+dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT
+MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK
+FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT
+21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID
+AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX
+QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+
+9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF
+9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT
+-----END CERTIFICATE-----
+""")
+
+intermediate_key_pem = b("""-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmKFGIb
+ljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT21H2
+qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwIDAQAB
+AoGAfSZVV80pSeOKHTYfbGdNY/jHdU9eFUa/33YWriXU+77EhpIItJjkRRgivIfo
+rhFJpBSGmDLblaqepm8emsXMeH4+2QzOYIf0QGGP6E6scjTt1PLqdqKfVJ1a2REN
+147cujNcmFJb/5VQHHMpaPTgttEjlzuww4+BCDPsVRABWrkCQQD3loH36nLoQTtf
++kQq0T6Bs9/UWkTAGo0ND81ALj0F8Ie1oeZg6RNT96RxZ3aVuFTESTv6/TbjWywO
+wdzlmV1vAkEA38rTJ6PTwaJlw5OttdDzAXGPB9tDmzh9oSi7cHwQQXizYd8MBYx4
+sjHUKD3dCQnb1dxJFhd3BT5HsnkRMbVZXQJAbXduH17ZTzcIOXc9jHDXYiFVZV5D
+52vV0WCbLzVCZc3jMrtSUKa8lPN5EWrdU3UchWybyG0MR5mX8S5lrF4SoQJAIyUD
+DBKaSqpqONCUUx1BTFS9FYrFjzbL4+c1qHCTTPTblt8kUCrDOZjBrKAqeiTmNSum
+/qUot9YUBF8m6BuGsQJATHHmdFy/fG1VLkyBp49CAa8tN3Z5r/CgTznI4DfMTf4C
+NbRHn2UmYlwQBa+L5lg9phewNe8aEwpPyPLoV85U8Q==
+-----END RSA PRIVATE KEY-----
+""")
+
+server_cert_pem = b("""-----BEGIN CERTIFICATE-----
+MIICKDCCAZGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH
+VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz
+NzUzWhgPMjAxNzA2MTExMjM3NTNaMBgxFjAUBgNVBAMTDWxvdmVseSBzZXJ2ZXIw
+gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAL6m+G653V0tpBC/OKl22VxOi2Cv
+lK4TYu9LHSDP9uDVTe7V5D5Tl6qzFoRRx5pfmnkqT5B+W9byp2NU3FC5hLm5zSAr
+b45meUhjEJ/ifkZgbNUjHdBIGP9MAQUHZa5WKdkGIJvGAvs8UzUqlr4TBWQIB24+
+lJ+Ukk/CRgasrYwdAgMBAAGjNjA0MB0GA1UdDgQWBBS4kC7Ij0W1TZXZqXQFAM2e
+gKEG2DATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOBgQBh30Li
+dJ+NlxIOx5343WqIBka3UbsOb2kxWrbkVCrvRapCMLCASO4FqiKWM+L0VDBprqIp
+2mgpFQ6FHpoIENGvJhdEKpptQ5i7KaGhnDNTfdy3x1+h852G99f1iyj0RmbuFcM8
+uzujnS8YXWvM7DM1Ilozk4MzPug8jzFp5uhKCQ==
+-----END CERTIFICATE-----
+""")
+
+server_key_pem = normalize_privatekey_pem(b("""-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQC+pvhuud1dLaQQvzipdtlcTotgr5SuE2LvSx0gz/bg1U3u1eQ+
+U5eqsxaEUceaX5p5Kk+QflvW8qdjVNxQuYS5uc0gK2+OZnlIYxCf4n5GYGzVIx3Q
+SBj/TAEFB2WuVinZBiCbxgL7PFM1Kpa+EwVkCAduPpSflJJPwkYGrK2MHQIDAQAB
+AoGAbwuZ0AR6JveahBaczjfnSpiFHf+mve2UxoQdpyr6ROJ4zg/PLW5K/KXrC48G
+j6f3tXMrfKHcpEoZrQWUfYBRCUsGD5DCazEhD8zlxEHahIsqpwA0WWssJA2VOLEN
+j6DuV2pCFbw67rfTBkTSo32ahfXxEKev5KswZk0JIzH3ooECQQDgzS9AI89h0gs8
+Dt+1m11Rzqo3vZML7ZIyGApUzVan+a7hbc33nbGRkAXjHaUBJO31it/H6dTO+uwX
+msWwNG5ZAkEA2RyFKs5xR5USTFaKLWCgpH/ydV96KPOpBND7TKQx62snDenFNNbn
+FwwOhpahld+vqhYk+pfuWWUpQciE+Bu7ZQJASjfT4sQv4qbbKK/scePicnDdx9th
+4e1EeB9xwb+tXXXUo/6Bor/AcUNwfiQ6Zt9PZOK9sR3lMZSsP7rMi7kzuQJABie6
+1sXXjFH7nNJvRG4S39cIxq8YRYTy68II/dlB2QzGpKxV/POCxbJ/zu0CU79tuYK7
+NaeNCFfH3aeTrX0LyQJAMBWjWmeKM2G2sCExheeQK0ROnaBC8itCECD4Jsve4nqf
+r50+LF74iLXFwqysVCebPKMOpDWp/qQ1BbJQIPs7/A==
+-----END RSA PRIVATE KEY-----
+"""))
+
+intermediate_server_cert_pem = b("""-----BEGIN CERTIFICATE-----
+MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw
+ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
+CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
+biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV
+BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
+CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
+biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1
+iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4
++kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm
+biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw
+UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN
+3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6
+x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA==
+-----END CERTIFICATE-----
+""")
+
+intermediate_server_key_pem = b("""-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCqklnKB37DV9os6vWI4CZsGHHlJlZxMJn9mMdBMkzsa49PrbhC
+SqyLEWCFEp0NE7CnCcA/uAxG6QuqLLj6RG4ZPk5/IaCAv3mLbGoD7N6GOPTyVJOW
+8Yel48mALJNq8jLn4uOyPgMqcrK6HGZuJdNGsfzc0OCLFWQ5tMSaH85UrQIDAQAB
+AoGAIQ594j5zna3/9WaPsTgnmhlesVctt4AAx/n827DA4ayyuHFlXUuVhtoWR5Pk
+5ezj9mtYW8DyeCegABnsu2vZni/CdvU6uiS1Hv6qM1GyYDm9KWgovIP9rQCDSGaz
+d57IWVGxx7ODFkm3gN5nxnSBOFVHytuW1J7FBRnEsehRroECQQDXHFOv82JuXDcz
+z3+4c74IEURdOHcbycxlppmK9kFqm5lsUdydnnGW+mvwDk0APOB7Wg7vyFyr393e
+dpmBDCzNAkEAyv6tVbTKUYhSjW+QhabJo896/EqQEYUmtMXxk4cQnKeR/Ao84Rkf
+EqD5IykMUfUI0jJU4DGX+gWZ10a7kNbHYQJAVFCuHNFxS4Cpwo0aqtnzKoZaHY/8
+X9ABZfafSHCtw3Op92M+7ikkrOELXdS9KdKyyqbKJAKNEHF3LbOfB44WIQJAA2N4
+9UNNVUsXRbElEnYUS529CdUczo4QdVgQjkvk5RiPAUwSdBd9Q0xYnFOlFwEmIowg
+ipWJWe0aAlP18ZcEQQJBAL+5lekZ/GUdQoZ4HAsN5a9syrzavJ9VvU1KOOPorPZK
+nMRZbbQgP+aSB7yl6K0gaLaZ8XaK0pjxNBh6ASqg9f4=
+-----END RSA PRIVATE KEY-----
+""")
+
+client_cert_pem = b("""-----BEGIN CERTIFICATE-----
+MIICJjCCAY+gAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH
+VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz
+ODA1WhgPMjAxNzA2MTExMjM4MDVaMBYxFDASBgNVBAMTC3VnbHkgY2xpZW50MIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2
+rn+GrRHRiZ+xkCw/CGNhbtPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xK
+iku4G/KvnnmWdeJHqsiXeUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7Dtb
+oCRajYyHfluARQIDAQABozYwNDAdBgNVHQ4EFgQUNQB+qkaOaEVecf1J3TTUtAff
+0fAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEAyv/Jh7gM
+Q3OHvmsFEEvRI+hsW8y66zK4K5de239Y44iZrFYkt7Q5nBPMEWDj4F2hLYWL/qtI
+9Zdr0U4UDCU9SmmGYh4o7R4TZ5pGFvBYvjhHbkSFYFQXZxKUi+WUxplP6I0wr2KJ
+PSTJCjJOn3xo2NTKRgV1gaoTf2EhL+RG8TQ=
+-----END CERTIFICATE-----
+""")
+
+client_key_pem = normalize_privatekey_pem(b("""-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2rn+GrRHRiZ+xkCw/CGNh
+btPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xKiku4G/KvnnmWdeJHqsiX
+eUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7DtboCRajYyHfluARQIDAQAB
+AoGATkZ+NceY5Glqyl4mD06SdcKfV65814vg2EL7V9t8+/mi9rYL8KztSXGlQWPX
+zuHgtRoMl78yQ4ZJYOBVo+nsx8KZNRCEBlE19bamSbQLCeQMenWnpeYyQUZ908gF
+h6L9qsFVJepgA9RDgAjyDoS5CaWCdCCPCH2lDkdcqC54SVUCQQDseuduc4wi8h4t
+V8AahUn9fn9gYfhoNuM0gdguTA0nPLVWz4hy1yJiWYQe0H7NLNNTmCKiLQaJpAbb
+TC6vE8C7AkEA0Ee8CMJUc20BnGEmxwgWcVuqFWaKCo8jTH1X38FlATUsyR3krjW2
+dL3yDD9NwHxsYP7nTKp/U8MV7U9IBn4y/wJBAJl7H0/BcLeRmuJk7IqJ7b635iYB
+D/9beFUw3MUXmQXZUfyYz39xf6CDZsu1GEdEC5haykeln3Of4M9d/4Kj+FcCQQCY
+si6xwT7GzMDkk/ko684AV3KPc/h6G0yGtFIrMg7J3uExpR/VdH2KgwMkZXisSMvw
+JJEQjOMCVsEJlRk54WWjAkEAzoZNH6UhDdBK5F38rVt/y4SEHgbSfJHIAmPS32Kq
+f6GGcfNpip0Uk7q7udTKuX7Q/buZi/C4YW7u3VKAquv9NA==
+-----END RSA PRIVATE KEY-----
+"""))
+
+cleartextCertificatePEM = b("""-----BEGIN CERTIFICATE-----
+MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU
+ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2
+NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM
+MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U
+ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL
+urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy
+2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF
+1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE
+FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn
+VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE
+BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS
+b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB
+AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi
+hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY
+w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn
+-----END CERTIFICATE-----
+""")
+
+cleartextPrivateKeyPEM = normalize_privatekey_pem(b("""\
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA
+jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU
+3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB
+AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB
+yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa
+6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM
+BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD
+u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk
+PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr
+I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8
+ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3
+6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2
+cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq
+-----END RSA PRIVATE KEY-----
+"""))
+
+cleartextCertificateRequestPEM = b("""-----BEGIN CERTIFICATE REQUEST-----
+MIIBnjCCAQcCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQH
+EwdDaGljYWdvMRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEXMBUGA1UEAxMORnJl
+ZGVyaWNrIERlYW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANp6Y17WzKSw
+BsUWkXdqg6tnXy8H8hA1msCMWpc+/2KJ4mbv5NyD6UD+/SqagQqulPbF/DFea9nA
+E0zhmHJELcM8gUTIlXv/cgDWnmK4xj8YkjVUiCdqKRAKeuzLG1pGmwwF5lGeJpXN
+xQn5ecR0UYSOWj6TTGXB9VyUMQzCClcBAgMBAAGgADANBgkqhkiG9w0BAQUFAAOB
+gQAAJGuF/R/GGbeC7FbFW+aJgr9ee0Xbl6nlhu7pTe67k+iiKT2dsl2ti68MVTnu
+Vrb3HUNqOkiwsJf6kCtq5oPn3QVYzTa76Dt2y3Rtzv6boRSlmlfrgS92GNma8JfR
+oICQk3nAudi6zl1Dix3BCv1pUp5KMtGn3MeDEi6QFGy2rA==
+-----END CERTIFICATE REQUEST-----
+""")
+
+encryptedPrivateKeyPEM = b("""-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,9573604A18579E9E
+
+SHOho56WxDkT0ht10UTeKc0F5u8cqIa01kzFAmETw0MAs8ezYtK15NPdCXUm3X/2
+a17G7LSF5bkxOgZ7vpXyMzun/owrj7CzvLxyncyEFZWvtvzaAhPhvTJtTIB3kf8B
+8+qRcpTGK7NgXEgYBW5bj1y4qZkD4zCL9o9NQzsKI3Ie8i0239jsDOWR38AxjXBH
+mGwAQ4Z6ZN5dnmM4fhMIWsmFf19sNyAML4gHenQCHhmXbjXeVq47aC2ProInJbrm
++00TcisbAQ40V9aehVbcDKtS4ZbMVDwncAjpXpcncC54G76N6j7F7wL7L/FuXa3A
+fvSVy9n2VfF/pJ3kYSflLHH2G/DFxjF7dl0GxhKPxJjp3IJi9VtuvmN9R2jZWLQF
+tfC8dXgy/P9CfFQhlinqBTEwgH0oZ/d4k4NVFDSdEMaSdmBAjlHpc+Vfdty3HVnV
+rKXj//wslsFNm9kIwJGIgKUa/n2jsOiydrsk1mgH7SmNCb3YHgZhbbnq0qLat/HC
+gHDt3FHpNQ31QzzL3yrenFB2L9osIsnRsDTPFNi4RX4SpDgNroxOQmyzCCV6H+d4
+o1mcnNiZSdxLZxVKccq0AfRpHqpPAFnJcQHP6xyT9MZp6fBa0XkxDnt9kNU8H3Qw
+7SJWZ69VXjBUzMlQViLuaWMgTnL+ZVyFZf9hTF7U/ef4HMLMAVNdiaGG+G+AjCV/
+MbzjS007Oe4qqBnCWaFPSnJX6uLApeTbqAxAeyCql56ULW5x6vDMNC3dwjvS/CEh
+11n8RkgFIQA0AhuKSIg3CbuartRsJnWOLwgLTzsrKYL4yRog1RJrtw==
+-----END RSA PRIVATE KEY-----
+""")
+
+encryptedPrivateKeyPEMPassphrase = b("foobar")
+
+# Some PKCS#7 stuff. Generated with the openssl command line:
+#
+# openssl crl2pkcs7 -inform pem -outform pem -certfile s.pem -nocrl
+#
+# with a certificate and key (but the key should be irrelevant) in s.pem
+pkcs7Data = b("""\
+-----BEGIN PKCS7-----
+MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID
+BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G
+A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN
+MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA
+cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG
+A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx
+HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD
+SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI
+zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw
+LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G
+A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp
+65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y
+Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g
+Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv
+bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu
+VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG
+/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9
+Ho4EzbYCOaEAMQA=
+-----END PKCS7-----
+""")
+
+pkcs7DataASN1 = base64.b64decode(b"""
+MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID
+BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G
+A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN
+MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA
+cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG
+A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx
+HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD
+SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI
+zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw
+LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G
+A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp
+65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y
+Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g
+Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv
+bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu
+VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG
+/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9
+Ho4EzbYCOaEAMQA=
+""")
+
+crlData = b("""\
+-----BEGIN X509 CRL-----
+MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
+SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT
+D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI2MDQzNDU2WhcNMTIwOTI3MDI0MTUyWjA8
+MBUCAgOrGA8yMDA5MDcyNTIzMzQ1NlowIwICAQAYDzIwMDkwNzI1MjMzNDU2WjAM
+MAoGA1UdFQQDCgEEMA0GCSqGSIb3DQEBBAUAA4GBAEBt7xTs2htdD3d4ErrcGAw1
+4dKcVnIWTutoI7xxen26Wwvh8VCsT7i/UeP+rBl9rC/kfjWjzQk3/zleaarGTpBT
+0yp4HXRFFoRhhSE/hP+eteaPXRgrsNRLHe9ZDd69wmh7J1wMDb0m81RG7kqcbsid
+vrzEeLDRiiPl92dyyWmu
+-----END X509 CRL-----
+""")
+
+
+# A broken RSA private key which can be used to test the error path through
+# PKey.check.
+inconsistentPrivateKeyPEM = b("""-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEaAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+zIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNeH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+""")
+
+# certificate with NULL bytes in subjectAltName and common name
+
+nulbyteSubjectAltNamePEM = b("""-----BEGIN CERTIFICATE-----
+MIIE2DCCA8CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBxTELMAkGA1UEBhMCVVMx
+DzANBgNVBAgMBk9yZWdvbjESMBAGA1UEBwwJQmVhdmVydG9uMSMwIQYDVQQKDBpQ
+eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEgMB4GA1UECwwXUHl0aG9uIENvcmUg
+RGV2ZWxvcG1lbnQxJDAiBgNVBAMMG251bGwucHl0aG9uLm9yZwBleGFtcGxlLm9y
+ZzEkMCIGCSqGSIb3DQEJARYVcHl0aG9uLWRldkBweXRob24ub3JnMB4XDTEzMDgw
+NzEzMTE1MloXDTEzMDgwNzEzMTI1MlowgcUxCzAJBgNVBAYTAlVTMQ8wDQYDVQQI
+DAZPcmVnb24xEjAQBgNVBAcMCUJlYXZlcnRvbjEjMCEGA1UECgwaUHl0aG9uIFNv
+ZnR3YXJlIEZvdW5kYXRpb24xIDAeBgNVBAsMF1B5dGhvbiBDb3JlIERldmVsb3Bt
+ZW50MSQwIgYDVQQDDBtudWxsLnB5dGhvbi5vcmcAZXhhbXBsZS5vcmcxJDAiBgkq
+hkiG9w0BCQEWFXB5dGhvbi1kZXZAcHl0aG9uLm9yZzCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBALXq7cn7Rn1vO3aA3TrzA5QLp6bb7B3f/yN0CJ2XFj+j
+pHs+Gw6WWSUDpybiiKnPec33BFawq3kyblnBMjBU61ioy5HwQqVkJ8vUVjGIUq3P
+vX/wBmQfzCe4o4uM89gpHyUL9UYGG8oCRa17dgqcv7u5rg0Wq2B1rgY+nHwx3JIv
+KRrgSwyRkGzpN8WQ1yrXlxWjgI9de0mPVDDUlywcWze1q2kwaEPTM3hLAmD1PESA
+oY/n8A/RXoeeRs9i/Pm/DGUS8ZPINXk/yOzsR/XvvkTVroIeLZqfmFpnZeF0cHzL
+08LODkVJJ9zjLdT7SA4vnne4FEbAxDbKAq5qkYzaL4UCAwEAAaOB0DCBzTAMBgNV
+HRMBAf8EAjAAMB0GA1UdDgQWBBSIWlXAUv9hzVKjNQ/qWpwkOCL3XDALBgNVHQ8E
+BAMCBeAwgZAGA1UdEQSBiDCBhYIeYWx0bnVsbC5weXRob24ub3JnAGV4YW1wbGUu
+Y29tgSBudWxsQHB5dGhvbi5vcmcAdXNlckBleGFtcGxlLm9yZ4YpaHR0cDovL251
+bGwucHl0aG9uLm9yZwBodHRwOi8vZXhhbXBsZS5vcmeHBMAAAgGHECABDbgAAAAA
+AAAAAAAAAAEwDQYJKoZIhvcNAQEFBQADggEBAKxPRe99SaghcI6IWT7UNkJw9aO9
+i9eo0Fj2MUqxpKbdb9noRDy2CnHWf7EIYZ1gznXPdwzSN4YCjV5d+Q9xtBaowT0j
+HPERs1ZuytCNNJTmhyqZ8q6uzMLoht4IqH/FBfpvgaeC5tBTnTT0rD5A/olXeimk
+kX4LxlEx5RAvpGB2zZVRGr6LobD9rVK91xuHYNIxxxfEGE8tCCWjp0+3ksri9SXx
+VHWBnbM9YaL32u3hxm8sYB/Yb8WSBavJCWJJqRStVRHM1koZlJmXNx2BX4vPo6iW
+RFEIPQsFZRLrtnCAiEhyT8bC2s/Njlu6ly9gtJZWSV46Q3ZjBL4q9sHKqZQ=
+-----END CERTIFICATE-----""")
+
+
+class X509ExtTests(TestCase):
+ """
+ Tests for :py:class:`OpenSSL.crypto.X509Extension`.
+ """
+
+ def setUp(self):
+ """
+ Create a new private key and start a certificate request (for a test
+ method to finish in one way or another).
+ """
+ super(X509ExtTests, self).setUp()
+ # Basic setup stuff to generate a certificate
+ self.pkey = PKey()
+ self.pkey.generate_key(TYPE_RSA, 384)
+ self.req = X509Req()
+ self.req.set_pubkey(self.pkey)
+ # Authority good you have.
+ self.req.get_subject().commonName = "Yoda root CA"
+ self.x509 = X509()
+ self.subject = self.x509.get_subject()
+ self.subject.commonName = self.req.get_subject().commonName
+ self.x509.set_issuer(self.subject)
+ self.x509.set_pubkey(self.pkey)
+ now = b(datetime.now().strftime("%Y%m%d%H%M%SZ"))
+ expire = b((datetime.now() + timedelta(days=100)).strftime("%Y%m%d%H%M%SZ"))
+ self.x509.set_notBefore(now)
+ self.x509.set_notAfter(expire)
+
+
+ def tearDown(self):
+ """
+ Forget all of the pyOpenSSL objects so they can be garbage collected,
+ their memory released, and not interfere with the leak detection code.
+ """
+ self.pkey = self.req = self.x509 = self.subject = None
+ super(X509ExtTests, self).tearDown()
+
+
+ def test_str(self):
+ """
+ The string representation of :py:class:`X509Extension` instances as returned by
+ :py:data:`str` includes stuff.
+ """
+ # This isn't necessarily the best string representation. Perhaps it
+ # will be changed/improved in the future.
+ self.assertEquals(
+ str(X509Extension(b('basicConstraints'), True, b('CA:false'))),
+ 'CA:FALSE')
+
+
+ def test_type(self):
+ """
+ :py:class:`X509Extension` and :py:class:`X509ExtensionType` refer to the same type object
+ and can be used to create instances of that type.
+ """
+ self.assertIdentical(X509Extension, X509ExtensionType)
+ self.assertConsistentType(
+ X509Extension,
+ 'X509Extension', b('basicConstraints'), True, b('CA:true'))
+
+
+ def test_construction(self):
+ """
+ :py:class:`X509Extension` accepts an extension type name, a critical flag,
+ and an extension value and returns an :py:class:`X509ExtensionType` instance.
+ """
+ basic = X509Extension(b('basicConstraints'), True, b('CA:true'))
+ self.assertTrue(
+ isinstance(basic, X509ExtensionType),
+ "%r is of type %r, should be %r" % (
+ basic, type(basic), X509ExtensionType))
+
+ comment = X509Extension(
+ b('nsComment'), False, b('pyOpenSSL unit test'))
+ self.assertTrue(
+ isinstance(comment, X509ExtensionType),
+ "%r is of type %r, should be %r" % (
+ comment, type(comment), X509ExtensionType))
+
+
+ def test_invalid_extension(self):
+ """
+ :py:class:`X509Extension` raises something if it is passed a bad extension
+ name or value.
+ """
+ self.assertRaises(
+ Error, X509Extension, b('thisIsMadeUp'), False, b('hi'))
+ self.assertRaises(
+ Error, X509Extension, b('basicConstraints'), False, b('blah blah'))
+
+ # Exercise a weird one (an extension which uses the r2i method). This
+ # exercises the codepath that requires a non-NULL ctx to be passed to
+ # X509V3_EXT_nconf. It can't work now because we provide no
+ # configuration database. It might be made to work in the future.
+ self.assertRaises(
+ Error, X509Extension, b('proxyCertInfo'), True,
+ b('language:id-ppl-anyLanguage,pathlen:1,policy:text:AB'))
+
+
+ def test_get_critical(self):
+ """
+ :py:meth:`X509ExtensionType.get_critical` returns the value of the
+ extension's critical flag.
+ """
+ ext = X509Extension(b('basicConstraints'), True, b('CA:true'))
+ self.assertTrue(ext.get_critical())
+ ext = X509Extension(b('basicConstraints'), False, b('CA:true'))
+ self.assertFalse(ext.get_critical())
+
+
+ def test_get_short_name(self):
+ """
+ :py:meth:`X509ExtensionType.get_short_name` returns a string giving the short
+ type name of the extension.
+ """
+ ext = X509Extension(b('basicConstraints'), True, b('CA:true'))
+ self.assertEqual(ext.get_short_name(), b('basicConstraints'))
+ ext = X509Extension(b('nsComment'), True, b('foo bar'))
+ self.assertEqual(ext.get_short_name(), b('nsComment'))
+
+
+ def test_get_data(self):
+ """
+ :py:meth:`X509Extension.get_data` returns a string giving the data of the
+ extension.
+ """
+ ext = X509Extension(b('basicConstraints'), True, b('CA:true'))
+ # Expect to get back the DER encoded form of CA:true.
+ self.assertEqual(ext.get_data(), b('0\x03\x01\x01\xff'))
+
+
+ def test_get_data_wrong_args(self):
+ """
+ :py:meth:`X509Extension.get_data` raises :py:exc:`TypeError` if passed any arguments.
+ """
+ ext = X509Extension(b('basicConstraints'), True, b('CA:true'))
+ self.assertRaises(TypeError, ext.get_data, None)
+ self.assertRaises(TypeError, ext.get_data, "foo")
+ self.assertRaises(TypeError, ext.get_data, 7)
+
+
+ def test_unused_subject(self):
+ """
+ The :py:data:`subject` parameter to :py:class:`X509Extension` may be provided for an
+ extension which does not use it and is ignored in this case.
+ """
+ ext1 = X509Extension(
+ b('basicConstraints'), False, b('CA:TRUE'), subject=self.x509)
+ self.x509.add_extensions([ext1])
+ self.x509.sign(self.pkey, 'sha1')
+ # This is a little lame. Can we think of a better way?
+ text = dump_certificate(FILETYPE_TEXT, self.x509)
+ self.assertTrue(b('X509v3 Basic Constraints:') in text)
+ self.assertTrue(b('CA:TRUE') in text)
+
+
+ def test_subject(self):
+ """
+ If an extension requires a subject, the :py:data:`subject` parameter to
+ :py:class:`X509Extension` provides its value.
+ """
+ ext3 = X509Extension(
+ b('subjectKeyIdentifier'), False, b('hash'), subject=self.x509)
+ self.x509.add_extensions([ext3])
+ self.x509.sign(self.pkey, 'sha1')
+ text = dump_certificate(FILETYPE_TEXT, self.x509)
+ self.assertTrue(b('X509v3 Subject Key Identifier:') in text)
+
+
+ def test_missing_subject(self):
+ """
+ If an extension requires a subject and the :py:data:`subject` parameter is
+ given no value, something happens.
+ """
+ self.assertRaises(
+ Error, X509Extension, b('subjectKeyIdentifier'), False, b('hash'))
+
+
+ def test_invalid_subject(self):
+ """
+ If the :py:data:`subject` parameter is given a value which is not an
+ :py:class:`X509` instance, :py:exc:`TypeError` is raised.
+ """
+ for badObj in [True, object(), "hello", [], self]:
+ self.assertRaises(
+ TypeError,
+ X509Extension,
+ 'basicConstraints', False, 'CA:TRUE', subject=badObj)
+
+
+ def test_unused_issuer(self):
+ """
+ The :py:data:`issuer` parameter to :py:class:`X509Extension` may be provided for an
+ extension which does not use it and is ignored in this case.
+ """
+ ext1 = X509Extension(
+ b('basicConstraints'), False, b('CA:TRUE'), issuer=self.x509)
+ self.x509.add_extensions([ext1])
+ self.x509.sign(self.pkey, 'sha1')
+ text = dump_certificate(FILETYPE_TEXT, self.x509)
+ self.assertTrue(b('X509v3 Basic Constraints:') in text)
+ self.assertTrue(b('CA:TRUE') in text)
+
+
+ def test_issuer(self):
+ """
+ If an extension requires an issuer, the :py:data:`issuer` parameter to
+ :py:class:`X509Extension` provides its value.
+ """
+ ext2 = X509Extension(
+ b('authorityKeyIdentifier'), False, b('issuer:always'),
+ issuer=self.x509)
+ self.x509.add_extensions([ext2])
+ self.x509.sign(self.pkey, 'sha1')
+ text = dump_certificate(FILETYPE_TEXT, self.x509)
+ self.assertTrue(b('X509v3 Authority Key Identifier:') in text)
+ self.assertTrue(b('DirName:/CN=Yoda root CA') in text)
+
+
+ def test_missing_issuer(self):
+ """
+ If an extension requires an issue and the :py:data:`issuer` parameter is given
+ no value, something happens.
+ """
+ self.assertRaises(
+ Error,
+ X509Extension,
+ b('authorityKeyIdentifier'), False,
+ b('keyid:always,issuer:always'))
+
+
+ def test_invalid_issuer(self):
+ """
+ If the :py:data:`issuer` parameter is given a value which is not an
+ :py:class:`X509` instance, :py:exc:`TypeError` is raised.
+ """
+ for badObj in [True, object(), "hello", [], self]:
+ self.assertRaises(
+ TypeError,
+ X509Extension,
+ 'authorityKeyIdentifier', False, 'keyid:always,issuer:always',
+ issuer=badObj)
+
+
+
+class PKeyTests(TestCase):
+ """
+ Unit tests for :py:class:`OpenSSL.crypto.PKey`.
+ """
+ def test_type(self):
+ """
+ :py:class:`PKey` and :py:class:`PKeyType` refer to the same type object
+ and can be used to create instances of that type.
+ """
+ self.assertIdentical(PKey, PKeyType)
+ self.assertConsistentType(PKey, 'PKey')
+
+
+ def test_construction(self):
+ """
+ :py:class:`PKey` takes no arguments and returns a new :py:class:`PKey` instance.
+ """
+ self.assertRaises(TypeError, PKey, None)
+ key = PKey()
+ self.assertTrue(
+ isinstance(key, PKeyType),
+ "%r is of type %r, should be %r" % (key, type(key), PKeyType))
+
+
+ def test_pregeneration(self):
+ """
+ :py:attr:`PKeyType.bits` and :py:attr:`PKeyType.type` return :py:data:`0` before the key is
+ generated. :py:attr:`PKeyType.check` raises :py:exc:`TypeError` before the key is
+ generated.
+ """
+ key = PKey()
+ self.assertEqual(key.type(), 0)
+ self.assertEqual(key.bits(), 0)
+ self.assertRaises(TypeError, key.check)
+
+
+ def test_failedGeneration(self):
+ """
+ :py:meth:`PKeyType.generate_key` takes two arguments, the first giving the key
+ type as one of :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` and the second giving the
+ number of bits to generate. If an invalid type is specified or
+ generation fails, :py:exc:`Error` is raised. If an invalid number of bits is
+ specified, :py:exc:`ValueError` or :py:exc:`Error` is raised.
+ """
+ key = PKey()
+ self.assertRaises(TypeError, key.generate_key)
+ self.assertRaises(TypeError, key.generate_key, 1, 2, 3)
+ self.assertRaises(TypeError, key.generate_key, "foo", "bar")
+ self.assertRaises(Error, key.generate_key, -1, 0)
+
+ self.assertRaises(ValueError, key.generate_key, TYPE_RSA, -1)
+ self.assertRaises(ValueError, key.generate_key, TYPE_RSA, 0)
+
+ # XXX RSA generation for small values of bits is fairly buggy in a wide
+ # range of OpenSSL versions. I need to figure out what the safe lower
+ # bound for a reasonable number of OpenSSL versions is and explicitly
+ # check for that in the wrapper. The failure behavior is typically an
+ # infinite loop inside OpenSSL.
+
+ # self.assertRaises(Error, key.generate_key, TYPE_RSA, 2)
+
+ # XXX DSA generation seems happy with any number of bits. The DSS
+ # says bits must be between 512 and 1024 inclusive. OpenSSL's DSA
+ # generator doesn't seem to care about the upper limit at all. For
+ # the lower limit, it uses 512 if anything smaller is specified.
+ # So, it doesn't seem possible to make generate_key fail for
+ # TYPE_DSA with a bits argument which is at least an int.
+
+ # self.assertRaises(Error, key.generate_key, TYPE_DSA, -7)
+
+
+ def test_rsaGeneration(self):
+ """
+ :py:meth:`PKeyType.generate_key` generates an RSA key when passed
+ :py:data:`TYPE_RSA` as a type and a reasonable number of bits.
+ """
+ bits = 128
+ key = PKey()
+ key.generate_key(TYPE_RSA, bits)
+ self.assertEqual(key.type(), TYPE_RSA)
+ self.assertEqual(key.bits(), bits)
+ self.assertTrue(key.check())
+
+
+ def test_dsaGeneration(self):
+ """
+ :py:meth:`PKeyType.generate_key` generates a DSA key when passed
+ :py:data:`TYPE_DSA` as a type and a reasonable number of bits.
+ """
+ # 512 is a magic number. The DSS (Digital Signature Standard)
+ # allows a minimum of 512 bits for DSA. DSA_generate_parameters
+ # will silently promote any value below 512 to 512.
+ bits = 512
+ key = PKey()
+ key.generate_key(TYPE_DSA, bits)
+ # self.assertEqual(key.type(), TYPE_DSA)
+ # self.assertEqual(key.bits(), bits)
+ # self.assertRaises(TypeError, key.check)
+
+
+ def test_regeneration(self):
+ """
+ :py:meth:`PKeyType.generate_key` can be called multiple times on the same
+ key to generate new keys.
+ """
+ key = PKey()
+ for type, bits in [(TYPE_RSA, 512), (TYPE_DSA, 576)]:
+ key.generate_key(type, bits)
+ self.assertEqual(key.type(), type)
+ self.assertEqual(key.bits(), bits)
+
+
+ def test_inconsistentKey(self):
+ """
+ :py:`PKeyType.check` returns :py:exc:`Error` if the key is not consistent.
+ """
+ key = load_privatekey(FILETYPE_PEM, inconsistentPrivateKeyPEM)
+ self.assertRaises(Error, key.check)
+
+
+ def test_check_wrong_args(self):
+ """
+ :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if called with any arguments.
+ """
+ self.assertRaises(TypeError, PKey().check, None)
+ self.assertRaises(TypeError, PKey().check, object())
+ self.assertRaises(TypeError, PKey().check, 1)
+
+
+ def test_check_public_key(self):
+ """
+ :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if only the public
+ part of the key is available.
+ """
+ # A trick to get a public-only key
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ cert = X509()
+ cert.set_pubkey(key)
+ pub = cert.get_pubkey()
+ self.assertRaises(TypeError, pub.check)
+
+
+
+class X509NameTests(TestCase):
+ """
+ Unit tests for :py:class:`OpenSSL.crypto.X509Name`.
+ """
+ def _x509name(self, **attrs):
+ # XXX There's no other way to get a new X509Name yet.
+ name = X509().get_subject()
+ attrs = list(attrs.items())
+ # Make the order stable - order matters!
+ def key(attr):
+ return attr[1]
+ attrs.sort(key=key)
+ for k, v in attrs:
+ setattr(name, k, v)
+ return name
+
+
+ def test_type(self):
+ """
+ The type of X509Name objects is :py:class:`X509NameType`.
+ """
+ self.assertIdentical(X509Name, X509NameType)
+ self.assertEqual(X509NameType.__name__, 'X509Name')
+ self.assertTrue(isinstance(X509NameType, type))
+
+ name = self._x509name()
+ self.assertTrue(
+ isinstance(name, X509NameType),
+ "%r is of type %r, should be %r" % (
+ name, type(name), X509NameType))
+
+
+ def test_onlyStringAttributes(self):
+ """
+ Attempting to set a non-:py:data:`str` attribute name on an :py:class:`X509NameType`
+ instance causes :py:exc:`TypeError` to be raised.
+ """
+ name = self._x509name()
+ # Beyond these cases, you may also think that unicode should be
+ # rejected. Sorry, you're wrong. unicode is automatically converted to
+ # str outside of the control of X509Name, so there's no way to reject
+ # it.
+
+ # Also, this used to test str subclasses, but that test is less relevant
+ # now that the implementation is in Python instead of C. Also PyPy
+ # automatically converts str subclasses to str when they are passed to
+ # setattr, so we can't test it on PyPy. Apparently CPython does this
+ # sometimes as well.
+ self.assertRaises(TypeError, setattr, name, None, "hello")
+ self.assertRaises(TypeError, setattr, name, 30, "hello")
+
+
+ def test_setInvalidAttribute(self):
+ """
+ Attempting to set any attribute name on an :py:class:`X509NameType` instance for
+ which no corresponding NID is defined causes :py:exc:`AttributeError` to be
+ raised.
+ """
+ name = self._x509name()
+ self.assertRaises(AttributeError, setattr, name, "no such thing", None)
+
+
+ def test_attributes(self):
+ """
+ :py:class:`X509NameType` instances have attributes for each standard (?)
+ X509Name field.
+ """
+ name = self._x509name()
+ name.commonName = "foo"
+ self.assertEqual(name.commonName, "foo")
+ self.assertEqual(name.CN, "foo")
+ name.CN = "baz"
+ self.assertEqual(name.commonName, "baz")
+ self.assertEqual(name.CN, "baz")
+ name.commonName = "bar"
+ self.assertEqual(name.commonName, "bar")
+ self.assertEqual(name.CN, "bar")
+ name.CN = "quux"
+ self.assertEqual(name.commonName, "quux")
+ self.assertEqual(name.CN, "quux")
+
+
+ def test_copy(self):
+ """
+ :py:class:`X509Name` creates a new :py:class:`X509NameType` instance with all the same
+ attributes as an existing :py:class:`X509NameType` instance when called with
+ one.
+ """
+ name = self._x509name(commonName="foo", emailAddress="bar@example.com")
+
+ copy = X509Name(name)
+ self.assertEqual(copy.commonName, "foo")
+ self.assertEqual(copy.emailAddress, "bar@example.com")
+
+ # Mutate the copy and ensure the original is unmodified.
+ copy.commonName = "baz"
+ self.assertEqual(name.commonName, "foo")
+
+ # Mutate the original and ensure the copy is unmodified.
+ name.emailAddress = "quux@example.com"
+ self.assertEqual(copy.emailAddress, "bar@example.com")
+
+
+ def test_repr(self):
+ """
+ :py:func:`repr` passed an :py:class:`X509NameType` instance should return a string
+ containing a description of the type and the NIDs which have been set
+ on it.
+ """
+ name = self._x509name(commonName="foo", emailAddress="bar")
+ self.assertEqual(
+ repr(name),
+ "<X509Name object '/emailAddress=bar/CN=foo'>")
+
+
+ def test_comparison(self):
+ """
+ :py:class:`X509NameType` instances should compare based on their NIDs.
+ """
+ def _equality(a, b, assertTrue, assertFalse):
+ assertTrue(a == b, "(%r == %r) --> False" % (a, b))
+ assertFalse(a != b)
+ assertTrue(b == a)
+ assertFalse(b != a)
+
+ def assertEqual(a, b):
+ _equality(a, b, self.assertTrue, self.assertFalse)
+
+ # Instances compare equal to themselves.
+ name = self._x509name()
+ assertEqual(name, name)
+
+ # Empty instances should compare equal to each other.
+ assertEqual(self._x509name(), self._x509name())
+
+ # Instances with equal NIDs should compare equal to each other.
+ assertEqual(self._x509name(commonName="foo"),
+ self._x509name(commonName="foo"))
+
+ # Instance with equal NIDs set using different aliases should compare
+ # equal to each other.
+ assertEqual(self._x509name(commonName="foo"),
+ self._x509name(CN="foo"))
+
+ # Instances with more than one NID with the same values should compare
+ # equal to each other.
+ assertEqual(self._x509name(CN="foo", organizationalUnitName="bar"),
+ self._x509name(commonName="foo", OU="bar"))
+
+ def assertNotEqual(a, b):
+ _equality(a, b, self.assertFalse, self.assertTrue)
+
+ # Instances with different values for the same NID should not compare
+ # equal to each other.
+ assertNotEqual(self._x509name(CN="foo"),
+ self._x509name(CN="bar"))
+
+ # Instances with different NIDs should not compare equal to each other.
+ assertNotEqual(self._x509name(CN="foo"),
+ self._x509name(OU="foo"))
+
+ def _inequality(a, b, assertTrue, assertFalse):
+ assertTrue(a < b)
+ assertTrue(a <= b)
+ assertTrue(b > a)
+ assertTrue(b >= a)
+ assertFalse(a > b)
+ assertFalse(a >= b)
+ assertFalse(b < a)
+ assertFalse(b <= a)
+
+ def assertLessThan(a, b):
+ _inequality(a, b, self.assertTrue, self.assertFalse)
+
+ # An X509Name with a NID with a value which sorts less than the value
+ # of the same NID on another X509Name compares less than the other
+ # X509Name.
+ assertLessThan(self._x509name(CN="abc"),
+ self._x509name(CN="def"))
+
+ def assertGreaterThan(a, b):
+ _inequality(a, b, self.assertFalse, self.assertTrue)
+
+ # An X509Name with a NID with a value which sorts greater than the
+ # value of the same NID on another X509Name compares greater than the
+ # other X509Name.
+ assertGreaterThan(self._x509name(CN="def"),
+ self._x509name(CN="abc"))
+
+
+ def test_hash(self):
+ """
+ :py:meth:`X509Name.hash` returns an integer hash based on the value of the
+ name.
+ """
+ a = self._x509name(CN="foo")
+ b = self._x509name(CN="foo")
+ self.assertEqual(a.hash(), b.hash())
+ a.CN = "bar"
+ self.assertNotEqual(a.hash(), b.hash())
+
+
+ def test_der(self):
+ """
+ :py:meth:`X509Name.der` returns the DER encoded form of the name.
+ """
+ a = self._x509name(CN="foo", C="US")
+ self.assertEqual(
+ a.der(),
+ b('0\x1b1\x0b0\t\x06\x03U\x04\x06\x13\x02US'
+ '1\x0c0\n\x06\x03U\x04\x03\x13\x03foo'))
+
+
+ def test_get_components(self):
+ """
+ :py:meth:`X509Name.get_components` returns a :py:data:`list` of
+ two-tuples of :py:data:`str`
+ giving the NIDs and associated values which make up the name.
+ """
+ a = self._x509name()
+ self.assertEqual(a.get_components(), [])
+ a.CN = "foo"
+ self.assertEqual(a.get_components(), [(b("CN"), b("foo"))])
+ a.organizationalUnitName = "bar"
+ self.assertEqual(
+ a.get_components(),
+ [(b("CN"), b("foo")), (b("OU"), b("bar"))])
+
+
+ def test_load_nul_byte_attribute(self):
+ """
+ An :py:class:`OpenSSL.crypto.X509Name` from an
+ :py:class:`OpenSSL.crypto.X509` instance loaded from a file can have a
+ NUL byte in the value of one of its attributes.
+ """
+ cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM)
+ subject = cert.get_subject()
+ self.assertEqual(
+ "null.python.org\x00example.org", subject.commonName)
+
+
+ def test_setAttributeFailure(self):
+ """
+ If the value of an attribute cannot be set for some reason then
+ :py:class:`OpenSSL.crypto.Error` is raised.
+ """
+ name = self._x509name()
+ # This value is too long
+ self.assertRaises(Error, setattr, name, "O", b"x" * 512)
+
+
+
+class _PKeyInteractionTestsMixin:
+ """
+ Tests which involve another thing and a PKey.
+ """
+ def signable(self):
+ """
+ Return something with a :py:meth:`set_pubkey`, :py:meth:`set_pubkey`,
+ and :py:meth:`sign` method.
+ """
+ raise NotImplementedError()
+
+
+ def test_signWithUngenerated(self):
+ """
+ :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a
+ :py:class:`PKey` with no parts.
+ """
+ request = self.signable()
+ key = PKey()
+ self.assertRaises(ValueError, request.sign, key, GOOD_DIGEST)
+
+
+ def test_signWithPublicKey(self):
+ """
+ :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a
+ :py:class:`PKey` with no private part as the signing key.
+ """
+ request = self.signable()
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ request.set_pubkey(key)
+ pub = request.get_pubkey()
+ self.assertRaises(ValueError, request.sign, pub, GOOD_DIGEST)
+
+
+ def test_signWithUnknownDigest(self):
+ """
+ :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when passed a digest name which is
+ not known.
+ """
+ request = self.signable()
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ self.assertRaises(ValueError, request.sign, key, BAD_DIGEST)
+
+
+ def test_sign(self):
+ """
+ :py:meth:`X509Req.sign` succeeds when passed a private key object and a valid
+ digest function. :py:meth:`X509Req.verify` can be used to check the signature.
+ """
+ request = self.signable()
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ request.set_pubkey(key)
+ request.sign(key, GOOD_DIGEST)
+ # If the type has a verify method, cover that too.
+ if getattr(request, 'verify', None) is not None:
+ pub = request.get_pubkey()
+ self.assertTrue(request.verify(pub))
+ # Make another key that won't verify.
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ self.assertRaises(Error, request.verify, key)
+
+
+
+
+class X509ReqTests(TestCase, _PKeyInteractionTestsMixin):
+ """
+ Tests for :py:class:`OpenSSL.crypto.X509Req`.
+ """
+ def signable(self):
+ """
+ Create and return a new :py:class:`X509Req`.
+ """
+ return X509Req()
+
+
+ def test_type(self):
+ """
+ :py:obj:`X509Req` and :py:obj:`X509ReqType` refer to the same type object and can be
+ used to create instances of that type.
+ """
+ self.assertIdentical(X509Req, X509ReqType)
+ self.assertConsistentType(X509Req, 'X509Req')
+
+
+ def test_construction(self):
+ """
+ :py:obj:`X509Req` takes no arguments and returns an :py:obj:`X509ReqType` instance.
+ """
+ request = X509Req()
+ self.assertTrue(
+ isinstance(request, X509ReqType),
+ "%r is of type %r, should be %r" % (request, type(request), X509ReqType))
+
+
+ def test_version(self):
+ """
+ :py:obj:`X509ReqType.set_version` sets the X.509 version of the certificate
+ request. :py:obj:`X509ReqType.get_version` returns the X.509 version of
+ the certificate request. The initial value of the version is 0.
+ """
+ request = X509Req()
+ self.assertEqual(request.get_version(), 0)
+ request.set_version(1)
+ self.assertEqual(request.get_version(), 1)
+ request.set_version(3)
+ self.assertEqual(request.get_version(), 3)
+
+
+ def test_version_wrong_args(self):
+ """
+ :py:obj:`X509ReqType.set_version` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or with a non-:py:obj:`int` argument.
+ :py:obj:`X509ReqType.get_version` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ request = X509Req()
+ self.assertRaises(TypeError, request.set_version)
+ self.assertRaises(TypeError, request.set_version, "foo")
+ self.assertRaises(TypeError, request.set_version, 1, 2)
+ self.assertRaises(TypeError, request.get_version, None)
+
+
+ def test_get_subject(self):
+ """
+ :py:obj:`X509ReqType.get_subject` returns an :py:obj:`X509Name` for the subject of
+ the request and which is valid even after the request object is
+ otherwise dead.
+ """
+ request = X509Req()
+ subject = request.get_subject()
+ self.assertTrue(
+ isinstance(subject, X509NameType),
+ "%r is of type %r, should be %r" % (subject, type(subject), X509NameType))
+ subject.commonName = "foo"
+ self.assertEqual(request.get_subject().commonName, "foo")
+ del request
+ subject.commonName = "bar"
+ self.assertEqual(subject.commonName, "bar")
+
+
+ def test_get_subject_wrong_args(self):
+ """
+ :py:obj:`X509ReqType.get_subject` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ request = X509Req()
+ self.assertRaises(TypeError, request.get_subject, None)
+
+
+ def test_add_extensions(self):
+ """
+ :py:obj:`X509Req.add_extensions` accepts a :py:obj:`list` of :py:obj:`X509Extension`
+ instances and adds them to the X509 request.
+ """
+ request = X509Req()
+ request.add_extensions([
+ X509Extension(b('basicConstraints'), True, b('CA:false'))])
+ exts = request.get_extensions()
+ self.assertEqual(len(exts), 1)
+ self.assertEqual(exts[0].get_short_name(), b('basicConstraints'))
+ self.assertEqual(exts[0].get_critical(), 1)
+ self.assertEqual(exts[0].get_data(), b('0\x00'))
+
+
+ def test_get_extensions(self):
+ """
+ :py:obj:`X509Req.get_extensions` returns a :py:obj:`list` of
+ extensions added to this X509 request.
+ """
+ request = X509Req()
+ exts = request.get_extensions()
+ self.assertEqual(exts, [])
+ request.add_extensions([
+ X509Extension(b('basicConstraints'), True, b('CA:true')),
+ X509Extension(b('keyUsage'), False, b('digitalSignature'))])
+ exts = request.get_extensions()
+ self.assertEqual(len(exts), 2)
+ self.assertEqual(exts[0].get_short_name(), b('basicConstraints'))
+ self.assertEqual(exts[0].get_critical(), 1)
+ self.assertEqual(exts[0].get_data(), b('0\x03\x01\x01\xff'))
+ self.assertEqual(exts[1].get_short_name(), b('keyUsage'))
+ self.assertEqual(exts[1].get_critical(), 0)
+ self.assertEqual(exts[1].get_data(), b('\x03\x02\x07\x80'))
+
+
+ def test_add_extensions_wrong_args(self):
+ """
+ :py:obj:`X509Req.add_extensions` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or with a non-:py:obj:`list`. Or it raises :py:obj:`ValueError`
+ if called with a :py:obj:`list` containing objects other than :py:obj:`X509Extension`
+ instances.
+ """
+ request = X509Req()
+ self.assertRaises(TypeError, request.add_extensions)
+ self.assertRaises(TypeError, request.add_extensions, object())
+ self.assertRaises(ValueError, request.add_extensions, [object()])
+ self.assertRaises(TypeError, request.add_extensions, [], None)
+
+
+ def test_verify_wrong_args(self):
+ """
+ :py:obj:`X509Req.verify` raises :py:obj:`TypeError` if called with zero
+ arguments or more than one argument or if passed anything other than a
+ :py:obj:`PKey` instance as its single argument.
+ """
+ request = X509Req()
+ self.assertRaises(TypeError, request.verify)
+ self.assertRaises(TypeError, request.verify, object())
+ self.assertRaises(TypeError, request.verify, PKey(), object())
+
+
+ def test_verify_uninitialized_key(self):
+ """
+ :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called
+ with a :py:obj:`OpenSSL.crypto.PKey` which contains no key data.
+ """
+ request = X509Req()
+ pkey = PKey()
+ self.assertRaises(Error, request.verify, pkey)
+
+
+ def test_verify_wrong_key(self):
+ """
+ :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called
+ with a :py:obj:`OpenSSL.crypto.PKey` which does not represent the public
+ part of the key which signed the request.
+ """
+ request = X509Req()
+ pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ request.sign(pkey, GOOD_DIGEST)
+ another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem)
+ self.assertRaises(Error, request.verify, another_pkey)
+
+
+ def test_verify_success(self):
+ """
+ :py:obj:`X509Req.verify` returns :py:obj:`True` if called with a
+ :py:obj:`OpenSSL.crypto.PKey` which represents the public part of the key
+ which signed the request.
+ """
+ request = X509Req()
+ pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ request.sign(pkey, GOOD_DIGEST)
+ self.assertEqual(True, request.verify(pkey))
+
+
+
+class X509Tests(TestCase, _PKeyInteractionTestsMixin):
+ """
+ Tests for :py:obj:`OpenSSL.crypto.X509`.
+ """
+ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM
+
+ extpem = """
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAkegAwIBAgIJAJHFjlcCgnQzMA0GCSqGSIb3DQEBBQUAMEcxCzAJBgNV
+BAYTAlNFMRUwEwYDVQQIEwxXZXN0ZXJib3R0b20xEjAQBgNVBAoTCUNhdGFsb2dp
+eDENMAsGA1UEAxMEUm9vdDAeFw0wODA0MjIxNDQ1MzhaFw0wOTA0MjIxNDQ1Mzha
+MFQxCzAJBgNVBAYTAlNFMQswCQYDVQQIEwJXQjEUMBIGA1UEChMLT3Blbk1ldGFk
+aXIxIjAgBgNVBAMTGW5vZGUxLm9tMi5vcGVubWV0YWRpci5vcmcwgZ8wDQYJKoZI
+hvcNAQEBBQADgY0AMIGJAoGBAPIcQMrwbk2nESF/0JKibj9i1x95XYAOwP+LarwT
+Op4EQbdlI9SY+uqYqlERhF19w7CS+S6oyqx0DRZSk4Y9dZ9j9/xgm2u/f136YS1u
+zgYFPvfUs6PqYLPSM8Bw+SjJ+7+2+TN+Tkiof9WP1cMjodQwOmdsiRbR0/J7+b1B
+hec1AgMBAAGjgcQwgcEwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
+TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFIdHsBcMVVMbAO7j6NCj
+03HgLnHaMB8GA1UdIwQYMBaAFL2h9Bf9Mre4vTdOiHTGAt7BRY/8MEYGA1UdEQQ/
+MD2CDSouZXhhbXBsZS5vcmeCESoub20yLmV4bWFwbGUuY29thwSC7wgKgRNvbTJA
+b3Blbm1ldGFkaXIub3JnMA0GCSqGSIb3DQEBBQUAA4GBALd7WdXkp2KvZ7/PuWZA
+MPlIxyjS+Ly11+BNE0xGQRp9Wz+2lABtpgNqssvU156+HkKd02rGheb2tj7MX9hG
+uZzbwDAZzJPjzDQDD7d3cWsrVcfIdqVU7epHqIadnOF+X0ghJ39pAm6VVadnSXCt
+WpOdIpB8KksUTCzV591Nr1wd
+-----END CERTIFICATE-----
+ """
+ def signable(self):
+ """
+ Create and return a new :py:obj:`X509`.
+ """
+ return X509()
+
+
+ def test_type(self):
+ """
+ :py:obj:`X509` and :py:obj:`X509Type` refer to the same type object and can be used
+ to create instances of that type.
+ """
+ self.assertIdentical(X509, X509Type)
+ self.assertConsistentType(X509, 'X509')
+
+
+ def test_construction(self):
+ """
+ :py:obj:`X509` takes no arguments and returns an instance of :py:obj:`X509Type`.
+ """
+ certificate = X509()
+ self.assertTrue(
+ isinstance(certificate, X509Type),
+ "%r is of type %r, should be %r" % (certificate,
+ type(certificate),
+ X509Type))
+ self.assertEqual(type(X509Type).__name__, 'type')
+ self.assertEqual(type(certificate).__name__, 'X509')
+ self.assertEqual(type(certificate), X509Type)
+ self.assertEqual(type(certificate), X509)
+
+
+ def test_get_version_wrong_args(self):
+ """
+ :py:obj:`X509.get_version` raises :py:obj:`TypeError` if invoked with any arguments.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.get_version, None)
+
+
+ def test_set_version_wrong_args(self):
+ """
+ :py:obj:`X509.set_version` raises :py:obj:`TypeError` if invoked with the wrong number
+ of arguments or an argument not of type :py:obj:`int`.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.set_version)
+ self.assertRaises(TypeError, cert.set_version, None)
+ self.assertRaises(TypeError, cert.set_version, 1, None)
+
+
+ def test_version(self):
+ """
+ :py:obj:`X509.set_version` sets the certificate version number.
+ :py:obj:`X509.get_version` retrieves it.
+ """
+ cert = X509()
+ cert.set_version(1234)
+ self.assertEquals(cert.get_version(), 1234)
+
+
+ def test_get_serial_number_wrong_args(self):
+ """
+ :py:obj:`X509.get_serial_number` raises :py:obj:`TypeError` if invoked with any
+ arguments.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.get_serial_number, None)
+
+
+ def test_serial_number(self):
+ """
+ The serial number of an :py:obj:`X509Type` can be retrieved and modified with
+ :py:obj:`X509Type.get_serial_number` and :py:obj:`X509Type.set_serial_number`.
+ """
+ certificate = X509()
+ self.assertRaises(TypeError, certificate.set_serial_number)
+ self.assertRaises(TypeError, certificate.set_serial_number, 1, 2)
+ self.assertRaises(TypeError, certificate.set_serial_number, "1")
+ self.assertRaises(TypeError, certificate.set_serial_number, 5.5)
+ self.assertEqual(certificate.get_serial_number(), 0)
+ certificate.set_serial_number(1)
+ self.assertEqual(certificate.get_serial_number(), 1)
+ certificate.set_serial_number(2 ** 32 + 1)
+ self.assertEqual(certificate.get_serial_number(), 2 ** 32 + 1)
+ certificate.set_serial_number(2 ** 64 + 1)
+ self.assertEqual(certificate.get_serial_number(), 2 ** 64 + 1)
+ certificate.set_serial_number(2 ** 128 + 1)
+ self.assertEqual(certificate.get_serial_number(), 2 ** 128 + 1)
+
+
+ def _setBoundTest(self, which):
+ """
+ :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1
+ GENERALIZEDTIME and sets the beginning of the certificate's validity
+ period to it.
+ """
+ certificate = X509()
+ set = getattr(certificate, 'set_not' + which)
+ get = getattr(certificate, 'get_not' + which)
+
+ # Starts with no value.
+ self.assertEqual(get(), None)
+
+ # GMT (Or is it UTC?) -exarkun
+ when = b("20040203040506Z")
+ set(when)
+ self.assertEqual(get(), when)
+
+ # A plus two hours and thirty minutes offset
+ when = b("20040203040506+0530")
+ set(when)
+ self.assertEqual(get(), when)
+
+ # A minus one hour fifteen minutes offset
+ when = b("20040203040506-0115")
+ set(when)
+ self.assertEqual(get(), when)
+
+ # An invalid string results in a ValueError
+ self.assertRaises(ValueError, set, b("foo bar"))
+
+ # The wrong number of arguments results in a TypeError.
+ self.assertRaises(TypeError, set)
+ self.assertRaises(TypeError, set, b("20040203040506Z"), b("20040203040506Z"))
+ self.assertRaises(TypeError, get, b("foo bar"))
+
+
+ # XXX ASN1_TIME (not GENERALIZEDTIME)
+
+ def test_set_notBefore(self):
+ """
+ :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1
+ GENERALIZEDTIME and sets the beginning of the certificate's validity
+ period to it.
+ """
+ self._setBoundTest("Before")
+
+
+ def test_set_notAfter(self):
+ """
+ :py:obj:`X509Type.set_notAfter` takes a string in the format of an ASN1
+ GENERALIZEDTIME and sets the end of the certificate's validity period
+ to it.
+ """
+ self._setBoundTest("After")
+
+
+ def test_get_notBefore(self):
+ """
+ :py:obj:`X509Type.get_notBefore` returns a string in the format of an ASN1
+ GENERALIZEDTIME even for certificates which store it as UTCTIME
+ internally.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ self.assertEqual(cert.get_notBefore(), b("20090325123658Z"))
+
+
+ def test_get_notAfter(self):
+ """
+ :py:obj:`X509Type.get_notAfter` returns a string in the format of an ASN1
+ GENERALIZEDTIME even for certificates which store it as UTCTIME
+ internally.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ self.assertEqual(cert.get_notAfter(), b("20170611123658Z"))
+
+
+ def test_gmtime_adj_notBefore_wrong_args(self):
+ """
+ :py:obj:`X509Type.gmtime_adj_notBefore` raises :py:obj:`TypeError` if called with the
+ wrong number of arguments or a non-:py:obj:`int` argument.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.gmtime_adj_notBefore)
+ self.assertRaises(TypeError, cert.gmtime_adj_notBefore, None)
+ self.assertRaises(TypeError, cert.gmtime_adj_notBefore, 123, None)
+
+
+ def test_gmtime_adj_notBefore(self):
+ """
+ :py:obj:`X509Type.gmtime_adj_notBefore` changes the not-before timestamp to be
+ the current time plus the number of seconds passed in.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ now = datetime.utcnow() + timedelta(seconds=100)
+ cert.gmtime_adj_notBefore(100)
+ self.assertEqual(cert.get_notBefore(), b(now.strftime("%Y%m%d%H%M%SZ")))
+
+
+ def test_gmtime_adj_notAfter_wrong_args(self):
+ """
+ :py:obj:`X509Type.gmtime_adj_notAfter` raises :py:obj:`TypeError` if called with the
+ wrong number of arguments or a non-:py:obj:`int` argument.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.gmtime_adj_notAfter)
+ self.assertRaises(TypeError, cert.gmtime_adj_notAfter, None)
+ self.assertRaises(TypeError, cert.gmtime_adj_notAfter, 123, None)
+
+
+ def test_gmtime_adj_notAfter(self):
+ """
+ :py:obj:`X509Type.gmtime_adj_notAfter` changes the not-after timestamp to be
+ the current time plus the number of seconds passed in.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ now = datetime.utcnow() + timedelta(seconds=100)
+ cert.gmtime_adj_notAfter(100)
+ self.assertEqual(cert.get_notAfter(), b(now.strftime("%Y%m%d%H%M%SZ")))
+
+
+ def test_has_expired_wrong_args(self):
+ """
+ :py:obj:`X509Type.has_expired` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.has_expired, None)
+
+
+ def test_has_expired(self):
+ """
+ :py:obj:`X509Type.has_expired` returns :py:obj:`True` if the certificate's not-after
+ time is in the past.
+ """
+ cert = X509()
+ cert.gmtime_adj_notAfter(-1)
+ self.assertTrue(cert.has_expired())
+
+
+ def test_has_not_expired(self):
+ """
+ :py:obj:`X509Type.has_expired` returns :py:obj:`False` if the certificate's not-after
+ time is in the future.
+ """
+ cert = X509()
+ cert.gmtime_adj_notAfter(2)
+ self.assertFalse(cert.has_expired())
+
+
+ def test_digest(self):
+ """
+ :py:obj:`X509.digest` returns a string giving ":"-separated hex-encoded words
+ of the digest of the certificate.
+ """
+ cert = X509()
+ self.assertEqual(
+ # This is MD5 instead of GOOD_DIGEST because the digest algorithm
+ # actually matters to the assertion (ie, another arbitrary, good
+ # digest will not product the same digest).
+ cert.digest("MD5"),
+ b("A8:EB:07:F8:53:25:0A:F2:56:05:C5:A5:C4:C4:C7:15"))
+
+
+ def _extcert(self, pkey, extensions):
+ cert = X509()
+ cert.set_pubkey(pkey)
+ cert.get_subject().commonName = "Unit Tests"
+ cert.get_issuer().commonName = "Unit Tests"
+ when = b(datetime.now().strftime("%Y%m%d%H%M%SZ"))
+ cert.set_notBefore(when)
+ cert.set_notAfter(when)
+
+ cert.add_extensions(extensions)
+ return load_certificate(
+ FILETYPE_PEM, dump_certificate(FILETYPE_PEM, cert))
+
+
+ def test_extension_count(self):
+ """
+ :py:obj:`X509.get_extension_count` returns the number of extensions that are
+ present in the certificate.
+ """
+ pkey = load_privatekey(FILETYPE_PEM, client_key_pem)
+ ca = X509Extension(b('basicConstraints'), True, b('CA:FALSE'))
+ key = X509Extension(b('keyUsage'), True, b('digitalSignature'))
+ subjectAltName = X509Extension(
+ b('subjectAltName'), True, b('DNS:example.com'))
+
+ # Try a certificate with no extensions at all.
+ c = self._extcert(pkey, [])
+ self.assertEqual(c.get_extension_count(), 0)
+
+ # And a certificate with one
+ c = self._extcert(pkey, [ca])
+ self.assertEqual(c.get_extension_count(), 1)
+
+ # And a certificate with several
+ c = self._extcert(pkey, [ca, key, subjectAltName])
+ self.assertEqual(c.get_extension_count(), 3)
+
+
+ def test_get_extension(self):
+ """
+ :py:obj:`X509.get_extension` takes an integer and returns an :py:obj:`X509Extension`
+ corresponding to the extension at that index.
+ """
+ pkey = load_privatekey(FILETYPE_PEM, client_key_pem)
+ ca = X509Extension(b('basicConstraints'), True, b('CA:FALSE'))
+ key = X509Extension(b('keyUsage'), True, b('digitalSignature'))
+ subjectAltName = X509Extension(
+ b('subjectAltName'), False, b('DNS:example.com'))
+
+ cert = self._extcert(pkey, [ca, key, subjectAltName])
+
+ ext = cert.get_extension(0)
+ self.assertTrue(isinstance(ext, X509Extension))
+ self.assertTrue(ext.get_critical())
+ self.assertEqual(ext.get_short_name(), b('basicConstraints'))
+
+ ext = cert.get_extension(1)
+ self.assertTrue(isinstance(ext, X509Extension))
+ self.assertTrue(ext.get_critical())
+ self.assertEqual(ext.get_short_name(), b('keyUsage'))
+
+ ext = cert.get_extension(2)
+ self.assertTrue(isinstance(ext, X509Extension))
+ self.assertFalse(ext.get_critical())
+ self.assertEqual(ext.get_short_name(), b('subjectAltName'))
+
+ self.assertRaises(IndexError, cert.get_extension, -1)
+ self.assertRaises(IndexError, cert.get_extension, 4)
+ self.assertRaises(TypeError, cert.get_extension, "hello")
+
+
+ def test_nullbyte_subjectAltName(self):
+ """
+ The fields of a `subjectAltName` extension on an X509 may contain NUL
+ bytes and this value is reflected in the string representation of the
+ extension object.
+ """
+ cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM)
+
+ ext = cert.get_extension(3)
+ self.assertEqual(ext.get_short_name(), b('subjectAltName'))
+ self.assertEqual(
+ b("DNS:altnull.python.org\x00example.com, "
+ "email:null@python.org\x00user@example.org, "
+ "URI:http://null.python.org\x00http://example.org, "
+ "IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1\n"),
+ b(str(ext)))
+
+
+ def test_invalid_digest_algorithm(self):
+ """
+ :py:obj:`X509.digest` raises :py:obj:`ValueError` if called with an unrecognized hash
+ algorithm.
+ """
+ cert = X509()
+ self.assertRaises(ValueError, cert.digest, BAD_DIGEST)
+
+
+ def test_get_subject_wrong_args(self):
+ """
+ :py:obj:`X509.get_subject` raises :py:obj:`TypeError` if called with any arguments.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.get_subject, None)
+
+
+ def test_get_subject(self):
+ """
+ :py:obj:`X509.get_subject` returns an :py:obj:`X509Name` instance.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ subj = cert.get_subject()
+ self.assertTrue(isinstance(subj, X509Name))
+ self.assertEquals(
+ subj.get_components(),
+ [(b('C'), b('US')), (b('ST'), b('IL')), (b('L'), b('Chicago')),
+ (b('O'), b('Testing')), (b('CN'), b('Testing Root CA'))])
+
+
+ def test_set_subject_wrong_args(self):
+ """
+ :py:obj:`X509.set_subject` raises a :py:obj:`TypeError` if called with the wrong
+ number of arguments or an argument not of type :py:obj:`X509Name`.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.set_subject)
+ self.assertRaises(TypeError, cert.set_subject, None)
+ self.assertRaises(TypeError, cert.set_subject, cert.get_subject(), None)
+
+
+ def test_set_subject(self):
+ """
+ :py:obj:`X509.set_subject` changes the subject of the certificate to the one
+ passed in.
+ """
+ cert = X509()
+ name = cert.get_subject()
+ name.C = 'AU'
+ name.O = 'Unit Tests'
+ cert.set_subject(name)
+ self.assertEquals(
+ cert.get_subject().get_components(),
+ [(b('C'), b('AU')), (b('O'), b('Unit Tests'))])
+
+
+ def test_get_issuer_wrong_args(self):
+ """
+ :py:obj:`X509.get_issuer` raises :py:obj:`TypeError` if called with any arguments.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.get_issuer, None)
+
+
+ def test_get_issuer(self):
+ """
+ :py:obj:`X509.get_issuer` returns an :py:obj:`X509Name` instance.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ subj = cert.get_issuer()
+ self.assertTrue(isinstance(subj, X509Name))
+ comp = subj.get_components()
+ self.assertEquals(
+ comp,
+ [(b('C'), b('US')), (b('ST'), b('IL')), (b('L'), b('Chicago')),
+ (b('O'), b('Testing')), (b('CN'), b('Testing Root CA'))])
+
+
+ def test_set_issuer_wrong_args(self):
+ """
+ :py:obj:`X509.set_issuer` raises a :py:obj:`TypeError` if called with the wrong
+ number of arguments or an argument not of type :py:obj:`X509Name`.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.set_issuer)
+ self.assertRaises(TypeError, cert.set_issuer, None)
+ self.assertRaises(TypeError, cert.set_issuer, cert.get_issuer(), None)
+
+
+ def test_set_issuer(self):
+ """
+ :py:obj:`X509.set_issuer` changes the issuer of the certificate to the one
+ passed in.
+ """
+ cert = X509()
+ name = cert.get_issuer()
+ name.C = 'AU'
+ name.O = 'Unit Tests'
+ cert.set_issuer(name)
+ self.assertEquals(
+ cert.get_issuer().get_components(),
+ [(b('C'), b('AU')), (b('O'), b('Unit Tests'))])
+
+
+ def test_get_pubkey_uninitialized(self):
+ """
+ When called on a certificate with no public key, :py:obj:`X509.get_pubkey`
+ raises :py:obj:`OpenSSL.crypto.Error`.
+ """
+ cert = X509()
+ self.assertRaises(Error, cert.get_pubkey)
+
+
+ def test_subject_name_hash_wrong_args(self):
+ """
+ :py:obj:`X509.subject_name_hash` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ cert = X509()
+ self.assertRaises(TypeError, cert.subject_name_hash, None)
+
+
+ def test_subject_name_hash(self):
+ """
+ :py:obj:`X509.subject_name_hash` returns the hash of the certificate's subject
+ name.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ self.assertIn(
+ cert.subject_name_hash(),
+ [3350047874, # OpenSSL 0.9.8, MD5
+ 3278919224, # OpenSSL 1.0.0, SHA1
+ ])
+
+
+ def test_get_signature_algorithm(self):
+ """
+ :py:obj:`X509Type.get_signature_algorithm` returns a string which means
+ the algorithm used to sign the certificate.
+ """
+ cert = load_certificate(FILETYPE_PEM, self.pemData)
+ self.assertEqual(
+ b("sha1WithRSAEncryption"), cert.get_signature_algorithm())
+
+
+ def test_get_undefined_signature_algorithm(self):
+ """
+ :py:obj:`X509Type.get_signature_algorithm` raises :py:obj:`ValueError` if the
+ signature algorithm is undefined or unknown.
+ """
+ # This certificate has been modified to indicate a bogus OID in the
+ # signature algorithm field so that OpenSSL does not recognize it.
+ certPEM = b("""\
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAmigAwIBAgIBATAGBgJ8BQUAMHsxCzAJBgNVBAYTAlNHMREwDwYDVQQK
+EwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5
+cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0
+MS5jb20wHhcNMDAwOTEwMDk1MTMwWhcNMDIwOTEwMDk1MTMwWjBTMQswCQYDVQQG
+EwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsG
+CSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBI
+AkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5AdNgLzJ1/MfsQQJ7hHVeHmTAjM664V
++fXvwUGJLziCeBo1ysWLRnl8CQIDAQABo4IBBDCCAQAwCQYDVR0TBAIwADAsBglg
+hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
+BBYEFM+EgpK+eyZiwFU1aOPSbczbPSpVMIGlBgNVHSMEgZ0wgZqAFPuHI2nrnDqT
+FeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlw
+dG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtNMkNyeXB0byBDZXJ0
+aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tggEA
+MA0GCSqGSIb3DQEBBAUAA4GBADv8KpPo+gfJxN2ERK1Y1l17sz/ZhzoGgm5XCdbx
+jEY7xKfpQngV599k1xhl11IMqizDwu0855agrckg2MCTmOI9DZzDD77tAYb+Dk0O
+PEVk0Mk/V0aIsDE9bolfCi/i/QWZ3N8s5nTWMNyBBBmoSliWCm4jkkRZRD0ejgTN
+tgI5
+-----END CERTIFICATE-----
+""")
+ cert = load_certificate(FILETYPE_PEM, certPEM)
+ self.assertRaises(ValueError, cert.get_signature_algorithm)
+
+
+
+class X509StoreTests(TestCase):
+ """
+ Test for :py:obj:`OpenSSL.crypto.X509Store`.
+ """
+ def test_type(self):
+ """
+ :py:obj:`X509StoreType` is a type object.
+ """
+ self.assertIdentical(X509Store, X509StoreType)
+ self.assertConsistentType(X509Store, 'X509Store')
+
+
+ def test_add_cert_wrong_args(self):
+ store = X509Store()
+ self.assertRaises(TypeError, store.add_cert)
+ self.assertRaises(TypeError, store.add_cert, object())
+ self.assertRaises(TypeError, store.add_cert, X509(), object())
+
+
+ def test_add_cert(self):
+ """
+ :py:obj:`X509Store.add_cert` adds a :py:obj:`X509` instance to the
+ certificate store.
+ """
+ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM)
+ store = X509Store()
+ store.add_cert(cert)
+
+
+ def test_add_cert_rejects_duplicate(self):
+ """
+ :py:obj:`X509Store.add_cert` raises :py:obj:`OpenSSL.crypto.Error` if an
+ attempt is made to add the same certificate to the store more than once.
+ """
+ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM)
+ store = X509Store()
+ store.add_cert(cert)
+ self.assertRaises(Error, store.add_cert, cert)
+
+
+
+class PKCS12Tests(TestCase):
+ """
+ Test for :py:obj:`OpenSSL.crypto.PKCS12` and :py:obj:`OpenSSL.crypto.load_pkcs12`.
+ """
+ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM
+
+ def test_type(self):
+ """
+ :py:obj:`PKCS12Type` is a type object.
+ """
+ self.assertIdentical(PKCS12, PKCS12Type)
+ self.assertConsistentType(PKCS12, 'PKCS12')
+
+
+ def test_empty_construction(self):
+ """
+ :py:obj:`PKCS12` returns a new instance of :py:obj:`PKCS12` with no certificate,
+ private key, CA certificates, or friendly name.
+ """
+ p12 = PKCS12()
+ self.assertEqual(None, p12.get_certificate())
+ self.assertEqual(None, p12.get_privatekey())
+ self.assertEqual(None, p12.get_ca_certificates())
+ self.assertEqual(None, p12.get_friendlyname())
+
+
+ def test_type_errors(self):
+ """
+ The :py:obj:`PKCS12` setter functions (:py:obj:`set_certificate`, :py:obj:`set_privatekey`,
+ :py:obj:`set_ca_certificates`, and :py:obj:`set_friendlyname`) raise :py:obj:`TypeError`
+ when passed objects of types other than those expected.
+ """
+ p12 = PKCS12()
+ self.assertRaises(TypeError, p12.set_certificate, 3)
+ self.assertRaises(TypeError, p12.set_certificate, PKey())
+ self.assertRaises(TypeError, p12.set_certificate, X509)
+ self.assertRaises(TypeError, p12.set_privatekey, 3)
+ self.assertRaises(TypeError, p12.set_privatekey, 'legbone')
+ self.assertRaises(TypeError, p12.set_privatekey, X509())
+ self.assertRaises(TypeError, p12.set_ca_certificates, 3)
+ self.assertRaises(TypeError, p12.set_ca_certificates, X509())
+ self.assertRaises(TypeError, p12.set_ca_certificates, (3, 4))
+ self.assertRaises(TypeError, p12.set_ca_certificates, ( PKey(), ))
+ self.assertRaises(TypeError, p12.set_friendlyname, 6)
+ self.assertRaises(TypeError, p12.set_friendlyname, ('foo', 'bar'))
+
+
+ def test_key_only(self):
+ """
+ A :py:obj:`PKCS12` with only a private key can be exported using
+ :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`.
+ """
+ passwd = b"blah"
+ p12 = PKCS12()
+ pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ p12.set_privatekey(pkey)
+ self.assertEqual(None, p12.get_certificate())
+ self.assertEqual(pkey, p12.get_privatekey())
+ try:
+ dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3)
+ except Error:
+ # Some versions of OpenSSL will throw an exception
+ # for this nearly useless PKCS12 we tried to generate:
+ # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')]
+ return
+ p12 = load_pkcs12(dumped_p12, passwd)
+ self.assertEqual(None, p12.get_ca_certificates())
+ self.assertEqual(None, p12.get_certificate())
+
+ # OpenSSL fails to bring the key back to us. So sad. Perhaps in the
+ # future this will be improved.
+ self.assertTrue(isinstance(p12.get_privatekey(), (PKey, type(None))))
+
+
+ def test_cert_only(self):
+ """
+ A :py:obj:`PKCS12` with only a certificate can be exported using
+ :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`.
+ """
+ passwd = b"blah"
+ p12 = PKCS12()
+ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM)
+ p12.set_certificate(cert)
+ self.assertEqual(cert, p12.get_certificate())
+ self.assertEqual(None, p12.get_privatekey())
+ try:
+ dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3)
+ except Error:
+ # Some versions of OpenSSL will throw an exception
+ # for this nearly useless PKCS12 we tried to generate:
+ # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')]
+ return
+ p12 = load_pkcs12(dumped_p12, passwd)
+ self.assertEqual(None, p12.get_privatekey())
+
+ # OpenSSL fails to bring the cert back to us. Groany mcgroan.
+ self.assertTrue(isinstance(p12.get_certificate(), (X509, type(None))))
+
+ # Oh ho. It puts the certificate into the ca certificates list, in
+ # fact. Totally bogus, I would think. Nevertheless, let's exploit
+ # that to check to see if it reconstructed the certificate we expected
+ # it to. At some point, hopefully this will change so that
+ # p12.get_certificate() is actually what returns the loaded
+ # certificate.
+ self.assertEqual(
+ cleartextCertificatePEM,
+ dump_certificate(FILETYPE_PEM, p12.get_ca_certificates()[0]))
+
+
+ def gen_pkcs12(self, cert_pem=None, key_pem=None, ca_pem=None, friendly_name=None):
+ """
+ Generate a PKCS12 object with components from PEM. Verify that the set
+ functions return None.
+ """
+ p12 = PKCS12()
+ if cert_pem:
+ ret = p12.set_certificate(load_certificate(FILETYPE_PEM, cert_pem))
+ self.assertEqual(ret, None)
+ if key_pem:
+ ret = p12.set_privatekey(load_privatekey(FILETYPE_PEM, key_pem))
+ self.assertEqual(ret, None)
+ if ca_pem:
+ ret = p12.set_ca_certificates((load_certificate(FILETYPE_PEM, ca_pem),))
+ self.assertEqual(ret, None)
+ if friendly_name:
+ ret = p12.set_friendlyname(friendly_name)
+ self.assertEqual(ret, None)
+ return p12
+
+
+ def check_recovery(self, p12_str, key=None, cert=None, ca=None, passwd=b"",
+ extra=()):
+ """
+ Use openssl program to confirm three components are recoverable from a
+ PKCS12 string.
+ """
+ if key:
+ recovered_key = _runopenssl(
+ p12_str, b"pkcs12", b"-nocerts", b"-nodes", b"-passin",
+ b"pass:" + passwd, *extra)
+ self.assertEqual(recovered_key[-len(key):], key)
+ if cert:
+ recovered_cert = _runopenssl(
+ p12_str, b"pkcs12", b"-clcerts", b"-nodes", b"-passin",
+ b"pass:" + passwd, b"-nokeys", *extra)
+ self.assertEqual(recovered_cert[-len(cert):], cert)
+ if ca:
+ recovered_cert = _runopenssl(
+ p12_str, b"pkcs12", b"-cacerts", b"-nodes", b"-passin",
+ b"pass:" + passwd, b"-nokeys", *extra)
+ self.assertEqual(recovered_cert[-len(ca):], ca)
+
+
+ def verify_pkcs12_container(self, p12):
+ """
+ Verify that the PKCS#12 container contains the correct client
+ certificate and private key.
+
+ :param p12: The PKCS12 instance to verify.
+ :type p12: :py:class:`PKCS12`
+ """
+ cert_pem = dump_certificate(FILETYPE_PEM, p12.get_certificate())
+ key_pem = dump_privatekey(FILETYPE_PEM, p12.get_privatekey())
+ self.assertEqual(
+ (client_cert_pem, client_key_pem, None),
+ (cert_pem, key_pem, p12.get_ca_certificates()))
+
+
+ def test_load_pkcs12(self):
+ """
+ A PKCS12 string generated using the openssl command line can be loaded
+ with :py:obj:`load_pkcs12` and its components extracted and examined.
+ """
+ passwd = b"whatever"
+ pem = client_key_pem + client_cert_pem
+ p12_str = _runopenssl(
+ pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:" + passwd)
+ p12 = load_pkcs12(p12_str, passphrase=passwd)
+ self.verify_pkcs12_container(p12)
+
+
+ def test_load_pkcs12_text_passphrase(self):
+ """
+ A PKCS12 string generated using the openssl command line can be loaded
+ with :py:obj:`load_pkcs12` and its components extracted and examined.
+ Using text as passphrase instead of bytes. DeprecationWarning expected.
+ """
+ pem = client_key_pem + client_cert_pem
+ passwd = b"whatever"
+ p12_str = _runopenssl(pem, b"pkcs12", b"-export", b"-clcerts",
+ b"-passout", b"pass:" + passwd)
+ with catch_warnings(record=True) as w:
+ simplefilter("always")
+ p12 = load_pkcs12(p12_str, passphrase=b"whatever".decode("ascii"))
+
+ self.assertEqual(
+ "{0} for passphrase is no longer accepted, use bytes".format(
+ WARNING_TYPE_EXPECTED
+ ),
+ str(w[-1].message)
+ )
+ self.assertIs(w[-1].category, DeprecationWarning)
+
+ self.verify_pkcs12_container(p12)
+
+
+ def test_load_pkcs12_no_passphrase(self):
+ """
+ A PKCS12 string generated using openssl command line can be loaded with
+ :py:obj:`load_pkcs12` without a passphrase and its components extracted
+ and examined.
+ """
+ pem = client_key_pem + client_cert_pem
+ p12_str = _runopenssl(
+ pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:")
+ p12 = load_pkcs12(p12_str)
+ self.verify_pkcs12_container(p12)
+
+
+ def _dump_and_load(self, dump_passphrase, load_passphrase):
+ """
+ A helper method to dump and load a PKCS12 object.
+ """
+ p12 = self.gen_pkcs12(client_cert_pem, client_key_pem)
+ dumped_p12 = p12.export(passphrase=dump_passphrase, iter=2, maciter=3)
+ return load_pkcs12(dumped_p12, passphrase=load_passphrase)
+
+
+ def test_load_pkcs12_null_passphrase_load_empty(self):
+ """
+ A PKCS12 string can be dumped with a null passphrase, loaded with an
+ empty passphrase with :py:obj:`load_pkcs12`, and its components
+ extracted and examined.
+ """
+ self.verify_pkcs12_container(
+ self._dump_and_load(dump_passphrase=None, load_passphrase=b''))
+
+
+ def test_load_pkcs12_null_passphrase_load_null(self):
+ """
+ A PKCS12 string can be dumped with a null passphrase, loaded with a
+ null passphrase with :py:obj:`load_pkcs12`, and its components
+ extracted and examined.
+ """
+ self.verify_pkcs12_container(
+ self._dump_and_load(dump_passphrase=None, load_passphrase=None))
+
+
+ def test_load_pkcs12_empty_passphrase_load_empty(self):
+ """
+ A PKCS12 string can be dumped with an empty passphrase, loaded with an
+ empty passphrase with :py:obj:`load_pkcs12`, and its components
+ extracted and examined.
+ """
+ self.verify_pkcs12_container(
+ self._dump_and_load(dump_passphrase=b'', load_passphrase=b''))
+
+
+ def test_load_pkcs12_empty_passphrase_load_null(self):
+ """
+ A PKCS12 string can be dumped with an empty passphrase, loaded with a
+ null passphrase with :py:obj:`load_pkcs12`, and its components
+ extracted and examined.
+ """
+ self.verify_pkcs12_container(
+ self._dump_and_load(dump_passphrase=b'', load_passphrase=None))
+
+
+ def test_load_pkcs12_garbage(self):
+ """
+ :py:obj:`load_pkcs12` raises :py:obj:`OpenSSL.crypto.Error` when passed a string
+ which is not a PKCS12 dump.
+ """
+ passwd = 'whatever'
+ e = self.assertRaises(Error, load_pkcs12, b'fruit loops', passwd)
+ self.assertEqual( e.args[0][0][0], 'asn1 encoding routines')
+ self.assertEqual( len(e.args[0][0]), 3)
+
+
+ def test_replace(self):
+ """
+ :py:obj:`PKCS12.set_certificate` replaces the certificate in a PKCS12 cluster.
+ :py:obj:`PKCS12.set_privatekey` replaces the private key.
+ :py:obj:`PKCS12.set_ca_certificates` replaces the CA certificates.
+ """
+ p12 = self.gen_pkcs12(client_cert_pem, client_key_pem, root_cert_pem)
+ p12.set_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+ p12.set_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ root_cert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ client_cert = load_certificate(FILETYPE_PEM, client_cert_pem)
+ p12.set_ca_certificates([root_cert]) # not a tuple
+ self.assertEqual(1, len(p12.get_ca_certificates()))
+ self.assertEqual(root_cert, p12.get_ca_certificates()[0])
+ p12.set_ca_certificates([client_cert, root_cert])
+ self.assertEqual(2, len(p12.get_ca_certificates()))
+ self.assertEqual(client_cert, p12.get_ca_certificates()[0])
+ self.assertEqual(root_cert, p12.get_ca_certificates()[1])
+
+
+ def test_friendly_name(self):
+ """
+ The *friendlyName* of a PKCS12 can be set and retrieved via
+ :py:obj:`PKCS12.get_friendlyname` and :py:obj:`PKCS12_set_friendlyname`, and a
+ :py:obj:`PKCS12` with a friendly name set can be dumped with :py:obj:`PKCS12.export`.
+ """
+ passwd = b'Dogmeat[]{}!@#$%^&*()~`?/.,<>-_+=";:'
+ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
+ for friendly_name in [b('Serverlicious'), None, b('###')]:
+ p12.set_friendlyname(friendly_name)
+ self.assertEqual(p12.get_friendlyname(), friendly_name)
+ dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3)
+ reloaded_p12 = load_pkcs12(dumped_p12, passwd)
+ self.assertEqual(
+ p12.get_friendlyname(), reloaded_p12.get_friendlyname())
+ # We would use the openssl program to confirm the friendly
+ # name, but it is not possible. The pkcs12 command
+ # does not store the friendly name in the cert's
+ # alias, which we could then extract.
+ self.check_recovery(
+ dumped_p12, key=server_key_pem, cert=server_cert_pem,
+ ca=root_cert_pem, passwd=passwd)
+
+
+ def test_various_empty_passphrases(self):
+ """
+ Test that missing, None, and '' passphrases are identical for PKCS12
+ export.
+ """
+ p12 = self.gen_pkcs12(client_cert_pem, client_key_pem, root_cert_pem)
+ passwd = b""
+ dumped_p12_empty = p12.export(iter=2, maciter=0, passphrase=passwd)
+ dumped_p12_none = p12.export(iter=3, maciter=2, passphrase=None)
+ dumped_p12_nopw = p12.export(iter=9, maciter=4)
+ for dumped_p12 in [dumped_p12_empty, dumped_p12_none, dumped_p12_nopw]:
+ self.check_recovery(
+ dumped_p12, key=client_key_pem, cert=client_cert_pem,
+ ca=root_cert_pem, passwd=passwd)
+
+
+ def test_removing_ca_cert(self):
+ """
+ Passing :py:obj:`None` to :py:obj:`PKCS12.set_ca_certificates` removes all CA
+ certificates.
+ """
+ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
+ p12.set_ca_certificates(None)
+ self.assertEqual(None, p12.get_ca_certificates())
+
+
+ def test_export_without_mac(self):
+ """
+ Exporting a PKCS12 with a :py:obj:`maciter` of ``-1`` excludes the MAC
+ entirely.
+ """
+ passwd = b"Lake Michigan"
+ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
+ dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2)
+ self.check_recovery(
+ dumped_p12, key=server_key_pem, cert=server_cert_pem,
+ passwd=passwd, extra=(b"-nomacver",))
+
+
+ def test_load_without_mac(self):
+ """
+ Loading a PKCS12 without a MAC does something other than crash.
+ """
+ passwd = b"Lake Michigan"
+ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
+ dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2)
+ try:
+ recovered_p12 = load_pkcs12(dumped_p12, passwd)
+ # The person who generated this PCKS12 should be flogged,
+ # or better yet we should have a means to determine
+ # whether a PCKS12 had a MAC that was verified.
+ # Anyway, libopenssl chooses to allow it, so the
+ # pyopenssl binding does as well.
+ self.assertTrue(isinstance(recovered_p12, PKCS12))
+ except Error:
+ # Failing here with an exception is preferred as some openssl
+ # versions do.
+ pass
+
+
+ def test_zero_len_list_for_ca(self):
+ """
+ A PKCS12 with an empty CA certificates list can be exported.
+ """
+ passwd = 'Hobie 18'
+ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem)
+ # p12.set_ca_certificates([])
+ # self.assertEqual((), p12.get_ca_certificates())
+ # dumped_p12 = p12.export(passphrase=passwd, iter=3)
+ # self.check_recovery(
+ # dumped_p12, key=server_key_pem, cert=server_cert_pem,
+ # passwd=passwd)
+
+
+ def test_export_without_args(self):
+ """
+ All the arguments to :py:obj:`PKCS12.export` are optional.
+ """
+ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
+ dumped_p12 = p12.export() # no args
+ self.check_recovery(
+ dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"")
+
+
+ def test_export_without_bytes(self):
+ """
+ Test :py:obj:`PKCS12.export` with text not bytes as passphrase
+ """
+ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
+
+ with catch_warnings(record=True) as w:
+ simplefilter("always")
+ dumped_p12 = p12.export(passphrase=b"randomtext".decode("ascii"))
+ self.assertEqual(
+ "{0} for passphrase is no longer accepted, use bytes".format(
+ WARNING_TYPE_EXPECTED
+ ),
+ str(w[-1].message)
+ )
+ self.assertIs(w[-1].category, DeprecationWarning)
+ self.check_recovery(
+ dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"randomtext")
+
+
+ def test_key_cert_mismatch(self):
+ """
+ :py:obj:`PKCS12.export` raises an exception when a key and certificate
+ mismatch.
+ """
+ p12 = self.gen_pkcs12(server_cert_pem, client_key_pem, root_cert_pem)
+ self.assertRaises(Error, p12.export)
+
+
+
+# These quoting functions taken directly from Twisted's twisted.python.win32.
+_cmdLineQuoteRe = re.compile(br'(\\*)"')
+_cmdLineQuoteRe2 = re.compile(br'(\\+)\Z')
+def cmdLineQuote(s):
+ """
+ Internal method for quoting a single command-line argument.
+
+ See http://www.perlmonks.org/?node_id=764004
+
+ :type: :py:obj:`str`
+ :param s: A single unquoted string to quote for something that is expecting
+ cmd.exe-style quoting
+
+ :rtype: :py:obj:`str`
+ :return: A cmd.exe-style quoted string
+ """
+ s = _cmdLineQuoteRe2.sub(br"\1\1", _cmdLineQuoteRe.sub(br'\1\1\\"', s))
+ return b'"' + s + b'"'
+
+
+
+def quoteArguments(arguments):
+ """
+ Quote an iterable of command-line arguments for passing to CreateProcess or
+ a similar API. This allows the list passed to :py:obj:`reactor.spawnProcess` to
+ match the child process's :py:obj:`sys.argv` properly.
+
+ :type arguments: :py:obj:`iterable` of :py:obj:`str`
+ :param arguments: An iterable of unquoted arguments to quote
+
+ :rtype: :py:obj:`str`
+ :return: A space-delimited string containing quoted versions of :py:obj:`arguments`
+ """
+ return b' '.join(map(cmdLineQuote, arguments))
+
+
+
+def _runopenssl(pem, *args):
+ """
+ Run the command line openssl tool with the given arguments and write
+ the given PEM to its stdin. Not safe for quotes.
+ """
+ if os.name == 'posix':
+ command = b"openssl " + b" ".join([
+ (b"'" + arg.replace(b"'", b"'\\''") + b"'")
+ for arg in args])
+ else:
+ command = b"openssl " + quoteArguments(args)
+ proc = Popen(native(command), shell=True, stdin=PIPE, stdout=PIPE)
+ proc.stdin.write(pem)
+ proc.stdin.close()
+ output = proc.stdout.read()
+ proc.stdout.close()
+ proc.wait()
+ return output
+
+
+
+class FunctionTests(TestCase):
+ """
+ Tests for free-functions in the :py:obj:`OpenSSL.crypto` module.
+ """
+
+ def test_load_privatekey_invalid_format(self):
+ """
+ :py:obj:`load_privatekey` raises :py:obj:`ValueError` if passed an unknown filetype.
+ """
+ self.assertRaises(ValueError, load_privatekey, 100, root_key_pem)
+
+
+ def test_load_privatekey_invalid_passphrase_type(self):
+ """
+ :py:obj:`load_privatekey` raises :py:obj:`TypeError` if passed a passphrase that is
+ neither a :py:obj:`str` nor a callable.
+ """
+ self.assertRaises(
+ TypeError,
+ load_privatekey,
+ FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object())
+
+
+ def test_load_privatekey_wrong_args(self):
+ """
+ :py:obj:`load_privatekey` raises :py:obj:`TypeError` if called with the wrong number
+ of arguments.
+ """
+ self.assertRaises(TypeError, load_privatekey)
+
+
+ def test_load_privatekey_wrongPassphrase(self):
+ """
+ :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it is passed an
+ encrypted PEM and an incorrect passphrase.
+ """
+ self.assertRaises(
+ Error,
+ load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, b("quack"))
+
+
+ def test_load_privatekey_passphraseWrongType(self):
+ """
+ :py:obj:`load_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase
+ with a private key encoded in a format, that doesn't support
+ encryption.
+ """
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ blob = dump_privatekey(FILETYPE_ASN1, key)
+ self.assertRaises(ValueError,
+ load_privatekey, FILETYPE_ASN1, blob, "secret")
+
+
+ def test_load_privatekey_passphrase(self):
+ """
+ :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM
+ string if given the passphrase.
+ """
+ key = load_privatekey(
+ FILETYPE_PEM, encryptedPrivateKeyPEM,
+ encryptedPrivateKeyPEMPassphrase)
+ self.assertTrue(isinstance(key, PKeyType))
+
+
+ def test_load_privatekey_passphrase_exception(self):
+ """
+ If the passphrase callback raises an exception, that exception is raised
+ by :py:obj:`load_privatekey`.
+ """
+ def cb(ignored):
+ raise ArithmeticError
+
+ self.assertRaises(ArithmeticError,
+ load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb)
+
+
+ def test_load_privatekey_wrongPassphraseCallback(self):
+ """
+ :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it
+ is passed an encrypted PEM and a passphrase callback which returns an
+ incorrect passphrase.
+ """
+ called = []
+ def cb(*a):
+ called.append(None)
+ return b("quack")
+ self.assertRaises(
+ Error,
+ load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb)
+ self.assertTrue(called)
+
+
+ def test_load_privatekey_passphraseCallback(self):
+ """
+ :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM
+ string if given a passphrase callback which returns the correct
+ password.
+ """
+ called = []
+ def cb(writing):
+ called.append(writing)
+ return encryptedPrivateKeyPEMPassphrase
+ key = load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb)
+ self.assertTrue(isinstance(key, PKeyType))
+ self.assertEqual(called, [False])
+
+
+ def test_load_privatekey_passphrase_wrong_return_type(self):
+ """
+ :py:obj:`load_privatekey` raises :py:obj:`ValueError` if the passphrase
+ callback returns something other than a byte string.
+ """
+ self.assertRaises(
+ ValueError,
+ load_privatekey,
+ FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3)
+
+
+ def test_dump_privatekey_wrong_args(self):
+ """
+ :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with the wrong number
+ of arguments.
+ """
+ self.assertRaises(TypeError, dump_privatekey)
+ # If cipher name is given, password is required.
+ self.assertRaises(
+ TypeError, dump_privatekey, FILETYPE_PEM, PKey(), GOOD_CIPHER)
+
+
+ def test_dump_privatekey_unknown_cipher(self):
+ """
+ :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized
+ cipher name.
+ """
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ self.assertRaises(
+ ValueError, dump_privatekey,
+ FILETYPE_PEM, key, BAD_CIPHER, "passphrase")
+
+
+ def test_dump_privatekey_invalid_passphrase_type(self):
+ """
+ :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with a passphrase which
+ is neither a :py:obj:`str` nor a callable.
+ """
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ self.assertRaises(
+ TypeError,
+ dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, object())
+
+
+ def test_dump_privatekey_invalid_filetype(self):
+ """
+ :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized
+ filetype.
+ """
+ key = PKey()
+ key.generate_key(TYPE_RSA, 512)
+ self.assertRaises(ValueError, dump_privatekey, 100, key)
+
+
+ def test_load_privatekey_passphraseCallbackLength(self):
+ """
+ :py:obj:`crypto.load_privatekey` should raise an error when the passphrase
+ provided by the callback is too long, not silently truncate it.
+ """
+ def cb(ignored):
+ return "a" * 1025
+
+ self.assertRaises(ValueError,
+ load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb)
+
+
+ def test_dump_privatekey_passphrase(self):
+ """
+ :py:obj:`dump_privatekey` writes an encrypted PEM when given a passphrase.
+ """
+ passphrase = b("foo")
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, passphrase)
+ self.assertTrue(isinstance(pem, binary_type))
+ loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase)
+ self.assertTrue(isinstance(loadedKey, PKeyType))
+ self.assertEqual(loadedKey.type(), key.type())
+ self.assertEqual(loadedKey.bits(), key.bits())
+
+
+ def test_dump_privatekey_passphraseWrongType(self):
+ """
+ :py:obj:`dump_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase
+ with a private key encoded in a format, that doesn't support
+ encryption.
+ """
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ self.assertRaises(ValueError,
+ dump_privatekey, FILETYPE_ASN1, key, GOOD_CIPHER, "secret")
+
+
+ def test_dump_certificate(self):
+ """
+ :py:obj:`dump_certificate` writes PEM, DER, and text.
+ """
+ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM
+ cert = load_certificate(FILETYPE_PEM, pemData)
+ dumped_pem = dump_certificate(FILETYPE_PEM, cert)
+ self.assertEqual(dumped_pem, cleartextCertificatePEM)
+ dumped_der = dump_certificate(FILETYPE_ASN1, cert)
+ good_der = _runopenssl(dumped_pem, b"x509", b"-outform", b"DER")
+ self.assertEqual(dumped_der, good_der)
+ cert2 = load_certificate(FILETYPE_ASN1, dumped_der)
+ dumped_pem2 = dump_certificate(FILETYPE_PEM, cert2)
+ self.assertEqual(dumped_pem2, cleartextCertificatePEM)
+ dumped_text = dump_certificate(FILETYPE_TEXT, cert)
+ good_text = _runopenssl(dumped_pem, b"x509", b"-noout", b"-text")
+ self.assertEqual(dumped_text, good_text)
+
+
+ def test_dump_privatekey_pem(self):
+ """
+ :py:obj:`dump_privatekey` writes a PEM
+ """
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ self.assertTrue(key.check())
+ dumped_pem = dump_privatekey(FILETYPE_PEM, key)
+ self.assertEqual(dumped_pem, cleartextPrivateKeyPEM)
+
+
+ def test_dump_privatekey_asn1(self):
+ """
+ :py:obj:`dump_privatekey` writes a DER
+ """
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ dumped_pem = dump_privatekey(FILETYPE_PEM, key)
+
+ dumped_der = dump_privatekey(FILETYPE_ASN1, key)
+ # XXX This OpenSSL call writes "writing RSA key" to standard out. Sad.
+ good_der = _runopenssl(dumped_pem, b"rsa", b"-outform", b"DER")
+ self.assertEqual(dumped_der, good_der)
+ key2 = load_privatekey(FILETYPE_ASN1, dumped_der)
+ dumped_pem2 = dump_privatekey(FILETYPE_PEM, key2)
+ self.assertEqual(dumped_pem2, cleartextPrivateKeyPEM)
+
+
+ def test_dump_privatekey_text(self):
+ """
+ :py:obj:`dump_privatekey` writes a text
+ """
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ dumped_pem = dump_privatekey(FILETYPE_PEM, key)
+
+ dumped_text = dump_privatekey(FILETYPE_TEXT, key)
+ good_text = _runopenssl(dumped_pem, b"rsa", b"-noout", b"-text")
+ self.assertEqual(dumped_text, good_text)
+
+
+ def test_dump_certificate_request(self):
+ """
+ :py:obj:`dump_certificate_request` writes a PEM, DER, and text.
+ """
+ req = load_certificate_request(FILETYPE_PEM, cleartextCertificateRequestPEM)
+ dumped_pem = dump_certificate_request(FILETYPE_PEM, req)
+ self.assertEqual(dumped_pem, cleartextCertificateRequestPEM)
+ dumped_der = dump_certificate_request(FILETYPE_ASN1, req)
+ good_der = _runopenssl(dumped_pem, b"req", b"-outform", b"DER")
+ self.assertEqual(dumped_der, good_der)
+ req2 = load_certificate_request(FILETYPE_ASN1, dumped_der)
+ dumped_pem2 = dump_certificate_request(FILETYPE_PEM, req2)
+ self.assertEqual(dumped_pem2, cleartextCertificateRequestPEM)
+ dumped_text = dump_certificate_request(FILETYPE_TEXT, req)
+ good_text = _runopenssl(dumped_pem, b"req", b"-noout", b"-text")
+ self.assertEqual(dumped_text, good_text)
+ self.assertRaises(ValueError, dump_certificate_request, 100, req)
+
+
+ def test_dump_privatekey_passphraseCallback(self):
+ """
+ :py:obj:`dump_privatekey` writes an encrypted PEM when given a callback which
+ returns the correct passphrase.
+ """
+ passphrase = b("foo")
+ called = []
+ def cb(writing):
+ called.append(writing)
+ return passphrase
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb)
+ self.assertTrue(isinstance(pem, binary_type))
+ self.assertEqual(called, [True])
+ loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase)
+ self.assertTrue(isinstance(loadedKey, PKeyType))
+ self.assertEqual(loadedKey.type(), key.type())
+ self.assertEqual(loadedKey.bits(), key.bits())
+
+
+ def test_dump_privatekey_passphrase_exception(self):
+ """
+ :py:obj:`dump_privatekey` should not overwrite the exception raised
+ by the passphrase callback.
+ """
+ def cb(ignored):
+ raise ArithmeticError
+
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ self.assertRaises(ArithmeticError,
+ dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb)
+
+
+ def test_dump_privatekey_passphraseCallbackLength(self):
+ """
+ :py:obj:`crypto.dump_privatekey` should raise an error when the passphrase
+ provided by the callback is too long, not silently truncate it.
+ """
+ def cb(ignored):
+ return "a" * 1025
+
+ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ self.assertRaises(ValueError,
+ dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb)
+
+
+ def test_load_pkcs7_data_pem(self):
+ """
+ :py:obj:`load_pkcs7_data` accepts a PKCS#7 string and returns an instance of
+ :py:obj:`PKCS7Type`.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertTrue(isinstance(pkcs7, PKCS7Type))
+
+
+ def test_load_pkcs7_data_asn1(self):
+ """
+ :py:obj:`load_pkcs7_data` accepts a bytes containing ASN1 data
+ representing PKCS#7 and returns an instance of :py:obj`PKCS7Type`.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_ASN1, pkcs7DataASN1)
+ self.assertTrue(isinstance(pkcs7, PKCS7Type))
+
+
+ def test_load_pkcs7_data_invalid(self):
+ """
+ If the data passed to :py:obj:`load_pkcs7_data` is invalid,
+ :py:obj:`Error` is raised.
+ """
+ self.assertRaises(Error, load_pkcs7_data, FILETYPE_PEM, b"foo")
+
+
+
+class LoadCertificateTests(TestCase):
+ """
+ Tests for :py:obj:`load_certificate_request`.
+ """
+ def test_badFileType(self):
+ """
+ If the file type passed to :py:obj:`load_certificate_request` is
+ neither :py:obj:`FILETYPE_PEM` nor :py:obj:`FILETYPE_ASN1` then
+ :py:class:`ValueError` is raised.
+ """
+ self.assertRaises(ValueError, load_certificate_request, object(), b"")
+
+
+
+class PKCS7Tests(TestCase):
+ """
+ Tests for :py:obj:`PKCS7Type`.
+ """
+ def test_type(self):
+ """
+ :py:obj:`PKCS7Type` is a type object.
+ """
+ self.assertTrue(isinstance(PKCS7Type, type))
+ self.assertEqual(PKCS7Type.__name__, 'PKCS7')
+
+ # XXX This doesn't currently work.
+ # self.assertIdentical(PKCS7, PKCS7Type)
+
+
+ # XXX Opposite results for all these following methods
+
+ def test_type_is_signed_wrong_args(self):
+ """
+ :py:obj:`PKCS7Type.type_is_signed` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertRaises(TypeError, pkcs7.type_is_signed, None)
+
+
+ def test_type_is_signed(self):
+ """
+ :py:obj:`PKCS7Type.type_is_signed` returns :py:obj:`True` if the PKCS7 object is of
+ the type *signed*.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertTrue(pkcs7.type_is_signed())
+
+
+ def test_type_is_enveloped_wrong_args(self):
+ """
+ :py:obj:`PKCS7Type.type_is_enveloped` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertRaises(TypeError, pkcs7.type_is_enveloped, None)
+
+
+ def test_type_is_enveloped(self):
+ """
+ :py:obj:`PKCS7Type.type_is_enveloped` returns :py:obj:`False` if the PKCS7 object is
+ not of the type *enveloped*.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertFalse(pkcs7.type_is_enveloped())
+
+
+ def test_type_is_signedAndEnveloped_wrong_args(self):
+ """
+ :py:obj:`PKCS7Type.type_is_signedAndEnveloped` raises :py:obj:`TypeError` if called
+ with any arguments.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertRaises(TypeError, pkcs7.type_is_signedAndEnveloped, None)
+
+
+ def test_type_is_signedAndEnveloped(self):
+ """
+ :py:obj:`PKCS7Type.type_is_signedAndEnveloped` returns :py:obj:`False` if the PKCS7
+ object is not of the type *signed and enveloped*.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertFalse(pkcs7.type_is_signedAndEnveloped())
+
+
+ def test_type_is_data(self):
+ """
+ :py:obj:`PKCS7Type.type_is_data` returns :py:obj:`False` if the PKCS7 object is not of
+ the type data.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertFalse(pkcs7.type_is_data())
+
+
+ def test_type_is_data_wrong_args(self):
+ """
+ :py:obj:`PKCS7Type.type_is_data` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertRaises(TypeError, pkcs7.type_is_data, None)
+
+
+ def test_get_type_name_wrong_args(self):
+ """
+ :py:obj:`PKCS7Type.get_type_name` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertRaises(TypeError, pkcs7.get_type_name, None)
+
+
+ def test_get_type_name(self):
+ """
+ :py:obj:`PKCS7Type.get_type_name` returns a :py:obj:`str` giving the type name.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertEquals(pkcs7.get_type_name(), b('pkcs7-signedData'))
+
+
+ def test_attribute(self):
+ """
+ If an attribute other than one of the methods tested here is accessed on
+ an instance of :py:obj:`PKCS7Type`, :py:obj:`AttributeError` is raised.
+ """
+ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
+ self.assertRaises(AttributeError, getattr, pkcs7, "foo")
+
+
+
+class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin):
+ """
+ Tests for :py:obj:`OpenSSL.crypto.NetscapeSPKI`.
+ """
+ def signable(self):
+ """
+ Return a new :py:obj:`NetscapeSPKI` for use with signing tests.
+ """
+ return NetscapeSPKI()
+
+
+ def test_type(self):
+ """
+ :py:obj:`NetscapeSPKI` and :py:obj:`NetscapeSPKIType` refer to the same type object
+ and can be used to create instances of that type.
+ """
+ self.assertIdentical(NetscapeSPKI, NetscapeSPKIType)
+ self.assertConsistentType(NetscapeSPKI, 'NetscapeSPKI')
+
+
+ def test_construction(self):
+ """
+ :py:obj:`NetscapeSPKI` returns an instance of :py:obj:`NetscapeSPKIType`.
+ """
+ nspki = NetscapeSPKI()
+ self.assertTrue(isinstance(nspki, NetscapeSPKIType))
+
+
+ def test_invalid_attribute(self):
+ """
+ Accessing a non-existent attribute of a :py:obj:`NetscapeSPKI` instance causes
+ an :py:obj:`AttributeError` to be raised.
+ """
+ nspki = NetscapeSPKI()
+ self.assertRaises(AttributeError, lambda: nspki.foo)
+
+
+ def test_b64_encode(self):
+ """
+ :py:obj:`NetscapeSPKI.b64_encode` encodes the certificate to a base64 blob.
+ """
+ nspki = NetscapeSPKI()
+ blob = nspki.b64_encode()
+ self.assertTrue(isinstance(blob, binary_type))
+
+
+
+class RevokedTests(TestCase):
+ """
+ Tests for :py:obj:`OpenSSL.crypto.Revoked`
+ """
+ def test_construction(self):
+ """
+ Confirm we can create :py:obj:`OpenSSL.crypto.Revoked`. Check
+ that it is empty.
+ """
+ revoked = Revoked()
+ self.assertTrue(isinstance(revoked, Revoked))
+ self.assertEquals(type(revoked), Revoked)
+ self.assertEquals(revoked.get_serial(), b('00'))
+ self.assertEquals(revoked.get_rev_date(), None)
+ self.assertEquals(revoked.get_reason(), None)
+
+
+ def test_construction_wrong_args(self):
+ """
+ Calling :py:obj:`OpenSSL.crypto.Revoked` with any arguments results
+ in a :py:obj:`TypeError` being raised.
+ """
+ self.assertRaises(TypeError, Revoked, None)
+ self.assertRaises(TypeError, Revoked, 1)
+ self.assertRaises(TypeError, Revoked, "foo")
+
+
+ def test_serial(self):
+ """
+ Confirm we can set and get serial numbers from
+ :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled
+ with grace.
+ """
+ revoked = Revoked()
+ ret = revoked.set_serial(b('10b'))
+ self.assertEquals(ret, None)
+ ser = revoked.get_serial()
+ self.assertEquals(ser, b('010B'))
+
+ revoked.set_serial(b('31ppp')) # a type error would be nice
+ ser = revoked.get_serial()
+ self.assertEquals(ser, b('31'))
+
+ self.assertRaises(ValueError, revoked.set_serial, b('pqrst'))
+ self.assertRaises(TypeError, revoked.set_serial, 100)
+ self.assertRaises(TypeError, revoked.get_serial, 1)
+ self.assertRaises(TypeError, revoked.get_serial, None)
+ self.assertRaises(TypeError, revoked.get_serial, "")
+
+
+ def test_date(self):
+ """
+ Confirm we can set and get revocation dates from
+ :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled
+ with grace.
+ """
+ revoked = Revoked()
+ date = revoked.get_rev_date()
+ self.assertEquals(date, None)
+
+ now = b(datetime.now().strftime("%Y%m%d%H%M%SZ"))
+ ret = revoked.set_rev_date(now)
+ self.assertEqual(ret, None)
+ date = revoked.get_rev_date()
+ self.assertEqual(date, now)
+
+
+ def test_reason(self):
+ """
+ Confirm we can set and get revocation reasons from
+ :py:obj:`OpenSSL.crypto.Revoked`. The "get" need to work
+ as "set". Likewise, each reason of all_reasons() must work.
+ """
+ revoked = Revoked()
+ for r in revoked.all_reasons():
+ for x in range(2):
+ ret = revoked.set_reason(r)
+ self.assertEquals(ret, None)
+ reason = revoked.get_reason()
+ self.assertEquals(
+ reason.lower().replace(b(' '), b('')),
+ r.lower().replace(b(' '), b('')))
+ r = reason # again with the resp of get
+
+ revoked.set_reason(None)
+ self.assertEqual(revoked.get_reason(), None)
+
+
+ def test_set_reason_wrong_arguments(self):
+ """
+ Calling :py:obj:`OpenSSL.crypto.Revoked.set_reason` with other than
+ one argument, or an argument which isn't a valid reason,
+ results in :py:obj:`TypeError` or :py:obj:`ValueError` being raised.
+ """
+ revoked = Revoked()
+ self.assertRaises(TypeError, revoked.set_reason, 100)
+ self.assertRaises(ValueError, revoked.set_reason, b('blue'))
+
+
+ def test_get_reason_wrong_arguments(self):
+ """
+ Calling :py:obj:`OpenSSL.crypto.Revoked.get_reason` with any
+ arguments results in :py:obj:`TypeError` being raised.
+ """
+ revoked = Revoked()
+ self.assertRaises(TypeError, revoked.get_reason, None)
+ self.assertRaises(TypeError, revoked.get_reason, 1)
+ self.assertRaises(TypeError, revoked.get_reason, "foo")
+
+
+
+class CRLTests(TestCase):
+ """
+ Tests for :py:obj:`OpenSSL.crypto.CRL`
+ """
+ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM)
+ pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+
+ def test_construction(self):
+ """
+ Confirm we can create :py:obj:`OpenSSL.crypto.CRL`. Check
+ that it is empty
+ """
+ crl = CRL()
+ self.assertTrue( isinstance(crl, CRL) )
+ self.assertEqual(crl.get_revoked(), None)
+
+
+ def test_construction_wrong_args(self):
+ """
+ Calling :py:obj:`OpenSSL.crypto.CRL` with any number of arguments
+ results in a :py:obj:`TypeError` being raised.
+ """
+ self.assertRaises(TypeError, CRL, 1)
+ self.assertRaises(TypeError, CRL, "")
+ self.assertRaises(TypeError, CRL, None)
+
+
+ def _get_crl(self):
+ """
+ Get a new ``CRL`` with a revocation.
+ """
+ crl = CRL()
+ revoked = Revoked()
+ now = b(datetime.now().strftime("%Y%m%d%H%M%SZ"))
+ revoked.set_rev_date(now)
+ revoked.set_serial(b('3ab'))
+ revoked.set_reason(b('sUpErSeDEd'))
+ crl.add_revoked(revoked)
+ return crl
+
+
+ def test_export_pem(self):
+ """
+ If not passed a format, ``CRL.export`` returns a "PEM" format string
+ representing a serial number, a revoked reason, and certificate issuer
+ information.
+ """
+ crl = self._get_crl()
+ # PEM format
+ dumped_crl = crl.export(self.cert, self.pkey, days=20)
+ text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+
+ # These magic values are based on the way the CRL above was constructed
+ # and with what certificate it was exported.
+ text.index(b('Serial Number: 03AB'))
+ text.index(b('Superseded'))
+ text.index(
+ b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA')
+ )
+
+
+ def test_export_der(self):
+ """
+ If passed ``FILETYPE_ASN1`` for the format, ``CRL.export`` returns a
+ "DER" format string representing a serial number, a revoked reason, and
+ certificate issuer information.
+ """
+ crl = self._get_crl()
+
+ # DER format
+ dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1)
+ text = _runopenssl(
+ dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER"
+ )
+ text.index(b('Serial Number: 03AB'))
+ text.index(b('Superseded'))
+ text.index(
+ b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA')
+ )
+
+
+ def test_export_text(self):
+ """
+ If passed ``FILETYPE_TEXT`` for the format, ``CRL.export`` returns a
+ text format string like the one produced by the openssl command line
+ tool.
+ """
+ crl = self._get_crl()
+
+ dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1)
+ text = _runopenssl(
+ dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER"
+ )
+
+ # text format
+ dumped_text = crl.export(self.cert, self.pkey, type=FILETYPE_TEXT)
+ self.assertEqual(text, dumped_text)
+
+
+ def test_export_custom_digest(self):
+ """
+ If passed the name of a digest function, ``CRL.export`` uses a
+ signature algorithm based on that digest function.
+ """
+ crl = self._get_crl()
+ dumped_crl = crl.export(self.cert, self.pkey, digest=b"sha1")
+ text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+ text.index(b('Signature Algorithm: sha1'))
+
+
+ def test_export_md5_digest(self):
+ """
+ If passed md5 as the digest function, ``CRL.export`` uses md5 and does
+ not emit a deprecation warning.
+ """
+ crl = self._get_crl()
+ with catch_warnings(record=True) as catcher:
+ simplefilter("always")
+ self.assertEqual(0, len(catcher))
+ dumped_crl = crl.export(self.cert, self.pkey, digest=b"md5")
+ text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+ text.index(b('Signature Algorithm: md5'))
+
+
+ def test_export_default_digest(self):
+ """
+ If not passed the name of a digest function, ``CRL.export`` uses a
+ signature algorithm based on MD5 and emits a deprecation warning.
+ """
+ crl = self._get_crl()
+ with catch_warnings(record=True) as catcher:
+ simplefilter("always")
+ dumped_crl = crl.export(self.cert, self.pkey)
+ self.assertEqual(
+ "The default message digest (md5) is deprecated. "
+ "Pass the name of a message digest explicitly.",
+ str(catcher[0].message),
+ )
+ text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+ text.index(b('Signature Algorithm: md5'))
+
+
+ def test_export_invalid(self):
+ """
+ If :py:obj:`CRL.export` is used with an uninitialized :py:obj:`X509`
+ instance, :py:obj:`OpenSSL.crypto.Error` is raised.
+ """
+ crl = CRL()
+ self.assertRaises(Error, crl.export, X509(), PKey())
+
+
+ def test_add_revoked_keyword(self):
+ """
+ :py:obj:`OpenSSL.CRL.add_revoked` accepts its single argument as the
+ ``revoked`` keyword argument.
+ """
+ crl = CRL()
+ revoked = Revoked()
+ crl.add_revoked(revoked=revoked)
+ self.assertTrue(isinstance(crl.get_revoked()[0], Revoked))
+
+
+ def test_export_wrong_args(self):
+ """
+ Calling :py:obj:`OpenSSL.CRL.export` with fewer than two or more than
+ four arguments, or with arguments other than the certificate,
+ private key, integer file type, and integer number of days it
+ expects, results in a :py:obj:`TypeError` being raised.
+ """
+ crl = CRL()
+ self.assertRaises(TypeError, crl.export)
+ self.assertRaises(TypeError, crl.export, self.cert)
+ self.assertRaises(TypeError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "md5", "foo")
+
+ self.assertRaises(TypeError, crl.export, None, self.pkey, FILETYPE_PEM, 10)
+ self.assertRaises(TypeError, crl.export, self.cert, None, FILETYPE_PEM, 10)
+ self.assertRaises(TypeError, crl.export, self.cert, self.pkey, None, 10)
+ self.assertRaises(TypeError, crl.export, self.cert, FILETYPE_PEM, None)
+
+
+ def test_export_unknown_filetype(self):
+ """
+ Calling :py:obj:`OpenSSL.CRL.export` with a file type other than
+ :py:obj:`FILETYPE_PEM`, :py:obj:`FILETYPE_ASN1`, or :py:obj:`FILETYPE_TEXT` results
+ in a :py:obj:`ValueError` being raised.
+ """
+ crl = CRL()
+ self.assertRaises(ValueError, crl.export, self.cert, self.pkey, 100, 10)
+
+
+ def test_export_unknown_digest(self):
+ """
+ Calling :py:obj:`OpenSSL.CRL.export` with a unsupported digest results
+ in a :py:obj:`ValueError` being raised.
+ """
+ crl = CRL()
+ self.assertRaises(
+ ValueError,
+ crl.export,
+ self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest"
+ )
+
+
+ def test_get_revoked(self):
+ """
+ Use python to create a simple CRL with two revocations.
+ Get back the :py:obj:`Revoked` using :py:obj:`OpenSSL.CRL.get_revoked` and
+ verify them.
+ """
+ crl = CRL()
+
+ revoked = Revoked()
+ now = b(datetime.now().strftime("%Y%m%d%H%M%SZ"))
+ revoked.set_rev_date(now)
+ revoked.set_serial(b('3ab'))
+ crl.add_revoked(revoked)
+ revoked.set_serial(b('100'))
+ revoked.set_reason(b('sUpErSeDEd'))
+ crl.add_revoked(revoked)
+
+ revs = crl.get_revoked()
+ self.assertEqual(len(revs), 2)
+ self.assertEqual(type(revs[0]), Revoked)
+ self.assertEqual(type(revs[1]), Revoked)
+ self.assertEqual(revs[0].get_serial(), b('03AB'))
+ self.assertEqual(revs[1].get_serial(), b('0100'))
+ self.assertEqual(revs[0].get_rev_date(), now)
+ self.assertEqual(revs[1].get_rev_date(), now)
+
+
+ def test_get_revoked_wrong_args(self):
+ """
+ Calling :py:obj:`OpenSSL.CRL.get_revoked` with any arguments results
+ in a :py:obj:`TypeError` being raised.
+ """
+ crl = CRL()
+ self.assertRaises(TypeError, crl.get_revoked, None)
+ self.assertRaises(TypeError, crl.get_revoked, 1)
+ self.assertRaises(TypeError, crl.get_revoked, "")
+ self.assertRaises(TypeError, crl.get_revoked, "", 1, None)
+
+
+ def test_add_revoked_wrong_args(self):
+ """
+ Calling :py:obj:`OpenSSL.CRL.add_revoked` with other than one
+ argument results in a :py:obj:`TypeError` being raised.
+ """
+ crl = CRL()
+ self.assertRaises(TypeError, crl.add_revoked)
+ self.assertRaises(TypeError, crl.add_revoked, 1, 2)
+ self.assertRaises(TypeError, crl.add_revoked, "foo", "bar")
+
+
+ def test_load_crl(self):
+ """
+ Load a known CRL and inspect its revocations. Both
+ PEM and DER formats are loaded.
+ """
+ crl = load_crl(FILETYPE_PEM, crlData)
+ revs = crl.get_revoked()
+ self.assertEqual(len(revs), 2)
+ self.assertEqual(revs[0].get_serial(), b('03AB'))
+ self.assertEqual(revs[0].get_reason(), None)
+ self.assertEqual(revs[1].get_serial(), b('0100'))
+ self.assertEqual(revs[1].get_reason(), b('Superseded'))
+
+ der = _runopenssl(crlData, b"crl", b"-outform", b"DER")
+ crl = load_crl(FILETYPE_ASN1, der)
+ revs = crl.get_revoked()
+ self.assertEqual(len(revs), 2)
+ self.assertEqual(revs[0].get_serial(), b('03AB'))
+ self.assertEqual(revs[0].get_reason(), None)
+ self.assertEqual(revs[1].get_serial(), b('0100'))
+ self.assertEqual(revs[1].get_reason(), b('Superseded'))
+
+
+ def test_load_crl_wrong_args(self):
+ """
+ Calling :py:obj:`OpenSSL.crypto.load_crl` with other than two
+ arguments results in a :py:obj:`TypeError` being raised.
+ """
+ self.assertRaises(TypeError, load_crl)
+ self.assertRaises(TypeError, load_crl, FILETYPE_PEM)
+ self.assertRaises(TypeError, load_crl, FILETYPE_PEM, crlData, None)
+
+
+ def test_load_crl_bad_filetype(self):
+ """
+ Calling :py:obj:`OpenSSL.crypto.load_crl` with an unknown file type
+ raises a :py:obj:`ValueError`.
+ """
+ self.assertRaises(ValueError, load_crl, 100, crlData)
+
+
+ def test_load_crl_bad_data(self):
+ """
+ Calling :py:obj:`OpenSSL.crypto.load_crl` with file data which can't
+ be loaded raises a :py:obj:`OpenSSL.crypto.Error`.
+ """
+ self.assertRaises(Error, load_crl, FILETYPE_PEM, b"hello, world")
+
+
+
+class X509StoreContextTests(TestCase):
+ """
+ Tests for :py:obj:`OpenSSL.crypto.X509StoreContext`.
+ """
+ root_cert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem)
+ intermediate_server_cert = load_certificate(FILETYPE_PEM, intermediate_server_cert_pem)
+
+ def test_valid(self):
+ """
+ :py:obj:`verify_certificate` returns ``None`` when called with a certificate
+ and valid chain.
+ """
+ store = X509Store()
+ store.add_cert(self.root_cert)
+ store.add_cert(self.intermediate_cert)
+ store_ctx = X509StoreContext(store, self.intermediate_server_cert)
+ self.assertEqual(store_ctx.verify_certificate(), None)
+
+
+ def test_reuse(self):
+ """
+ :py:obj:`verify_certificate` can be called multiple times with the same
+ ``X509StoreContext`` instance to produce the same result.
+ """
+ store = X509Store()
+ store.add_cert(self.root_cert)
+ store.add_cert(self.intermediate_cert)
+ store_ctx = X509StoreContext(store, self.intermediate_server_cert)
+ self.assertEqual(store_ctx.verify_certificate(), None)
+ self.assertEqual(store_ctx.verify_certificate(), None)
+
+
+ def test_trusted_self_signed(self):
+ """
+ :py:obj:`verify_certificate` returns ``None`` when called with a self-signed
+ certificate and itself in the chain.
+ """
+ store = X509Store()
+ store.add_cert(self.root_cert)
+ store_ctx = X509StoreContext(store, self.root_cert)
+ self.assertEqual(store_ctx.verify_certificate(), None)
+
+
+ def test_untrusted_self_signed(self):
+ """
+ :py:obj:`verify_certificate` raises error when a self-signed certificate is
+ verified without itself in the chain.
+ """
+ store = X509Store()
+ store_ctx = X509StoreContext(store, self.root_cert)
+ e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate)
+ self.assertEqual(e.args[0][2], 'self signed certificate')
+ self.assertEqual(e.certificate.get_subject().CN, 'Testing Root CA')
+
+
+ def test_invalid_chain_no_root(self):
+ """
+ :py:obj:`verify_certificate` raises error when a root certificate is missing
+ from the chain.
+ """
+ store = X509Store()
+ store.add_cert(self.intermediate_cert)
+ store_ctx = X509StoreContext(store, self.intermediate_server_cert)
+ e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate)
+ self.assertEqual(e.args[0][2], 'unable to get issuer certificate')
+ self.assertEqual(e.certificate.get_subject().CN, 'intermediate')
+
+
+ def test_invalid_chain_no_intermediate(self):
+ """
+ :py:obj:`verify_certificate` raises error when an intermediate certificate is
+ missing from the chain.
+ """
+ store = X509Store()
+ store.add_cert(self.root_cert)
+ store_ctx = X509StoreContext(store, self.intermediate_server_cert)
+ e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate)
+ self.assertEqual(e.args[0][2], 'unable to get local issuer certificate')
+ self.assertEqual(e.certificate.get_subject().CN, 'intermediate-service')
+
+
+ def test_modification_pre_verify(self):
+ """
+ :py:obj:`verify_certificate` can use a store context modified after
+ instantiation.
+ """
+ store_bad = X509Store()
+ store_bad.add_cert(self.intermediate_cert)
+ store_good = X509Store()
+ store_good.add_cert(self.root_cert)
+ store_good.add_cert(self.intermediate_cert)
+ store_ctx = X509StoreContext(store_bad, self.intermediate_server_cert)
+ e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate)
+ self.assertEqual(e.args[0][2], 'unable to get issuer certificate')
+ self.assertEqual(e.certificate.get_subject().CN, 'intermediate')
+ store_ctx.set_store(store_good)
+ self.assertEqual(store_ctx.verify_certificate(), None)
+
+
+
+class SignVerifyTests(TestCase):
+ """
+ Tests for :py:obj:`OpenSSL.crypto.sign` and :py:obj:`OpenSSL.crypto.verify`.
+ """
+ def test_sign_verify(self):
+ """
+ :py:obj:`sign` generates a cryptographic signature which :py:obj:`verify` can check.
+ """
+ content = b(
+ "It was a bright cold day in April, and the clocks were striking "
+ "thirteen. Winston Smith, his chin nuzzled into his breast in an "
+ "effort to escape the vile wind, slipped quickly through the "
+ "glass doors of Victory Mansions, though not quickly enough to "
+ "prevent a swirl of gritty dust from entering along with him.")
+
+ # sign the content with this private key
+ priv_key = load_privatekey(FILETYPE_PEM, root_key_pem)
+ # verify the content with this cert
+ good_cert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ # certificate unrelated to priv_key, used to trigger an error
+ bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ for digest in ['md5', 'sha1']:
+ sig = sign(priv_key, content, digest)
+
+ # Verify the signature of content, will throw an exception if error.
+ verify(good_cert, sig, content, digest)
+
+ # This should fail because the certificate doesn't match the
+ # private key that was used to sign the content.
+ self.assertRaises(Error, verify, bad_cert, sig, content, digest)
+
+ # This should fail because we've "tainted" the content after
+ # signing it.
+ self.assertRaises(
+ Error, verify,
+ good_cert, sig, content + b("tainted"), digest)
+
+ # test that unknown digest types fail
+ self.assertRaises(
+ ValueError, sign, priv_key, content, "strange-digest")
+ self.assertRaises(
+ ValueError, verify, good_cert, sig, content, "strange-digest")
+
+
+ def test_sign_verify_with_text(self):
+ """
+ :py:obj:`sign` generates a cryptographic signature which :py:obj:`verify` can check.
+ Deprecation warnings raised because using text instead of bytes as content
+ """
+ content = (
+ b"It was a bright cold day in April, and the clocks were striking "
+ b"thirteen. Winston Smith, his chin nuzzled into his breast in an "
+ b"effort to escape the vile wind, slipped quickly through the "
+ b"glass doors of Victory Mansions, though not quickly enough to "
+ b"prevent a swirl of gritty dust from entering along with him."
+ ).decode("ascii")
+
+ priv_key = load_privatekey(FILETYPE_PEM, root_key_pem)
+ cert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ for digest in ['md5', 'sha1']:
+ with catch_warnings(record=True) as w:
+ simplefilter("always")
+ sig = sign(priv_key, content, digest)
+
+ self.assertEqual(
+ "{0} for data is no longer accepted, use bytes".format(
+ WARNING_TYPE_EXPECTED
+ ),
+ str(w[-1].message)
+ )
+ self.assertIs(w[-1].category, DeprecationWarning)
+
+ with catch_warnings(record=True) as w:
+ simplefilter("always")
+ verify(cert, sig, content, digest)
+
+ self.assertEqual(
+ "{0} for data is no longer accepted, use bytes".format(
+ WARNING_TYPE_EXPECTED
+ ),
+ str(w[-1].message)
+ )
+ self.assertIs(w[-1].category, DeprecationWarning)
+
+
+ def test_sign_nulls(self):
+ """
+ :py:obj:`sign` produces a signature for a string with embedded nulls.
+ """
+ content = b("Watch out! \0 Did you see it?")
+ priv_key = load_privatekey(FILETYPE_PEM, root_key_pem)
+ good_cert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ sig = sign(priv_key, content, "sha1")
+ verify(good_cert, sig, content, "sha1")
+
+
+
+class EllipticCurveTests(TestCase):
+ """
+ Tests for :py:class:`_EllipticCurve`, :py:obj:`get_elliptic_curve`, and
+ :py:obj:`get_elliptic_curves`.
+ """
+ def test_set(self):
+ """
+ :py:obj:`get_elliptic_curves` returns a :py:obj:`set`.
+ """
+ self.assertIsInstance(get_elliptic_curves(), set)
+
+
+ def test_some_curves(self):
+ """
+ If :py:mod:`cryptography` has elliptic curve support then the set
+ returned by :py:obj:`get_elliptic_curves` has some elliptic curves in
+ it.
+
+ There could be an OpenSSL that violates this assumption. If so, this
+ test will fail and we'll find out.
+ """
+ curves = get_elliptic_curves()
+ if lib.Cryptography_HAS_EC:
+ self.assertTrue(curves)
+ else:
+ self.assertFalse(curves)
+
+
+ def test_a_curve(self):
+ """
+ :py:obj:`get_elliptic_curve` can be used to retrieve a particular
+ supported curve.
+ """
+ curves = get_elliptic_curves()
+ if curves:
+ curve = next(iter(curves))
+ self.assertEqual(curve.name, get_elliptic_curve(curve.name).name)
+ else:
+ self.assertRaises(ValueError, get_elliptic_curve, u("prime256v1"))
+
+
+ def test_not_a_curve(self):
+ """
+ :py:obj:`get_elliptic_curve` raises :py:class:`ValueError` if called
+ with a name which does not identify a supported curve.
+ """
+ self.assertRaises(
+ ValueError, get_elliptic_curve, u("this curve was just invented"))
+
+
+ def test_repr(self):
+ """
+ The string representation of a curve object includes simply states the
+ object is a curve and what its name is.
+ """
+ curves = get_elliptic_curves()
+ if curves:
+ curve = next(iter(curves))
+ self.assertEqual("<Curve %r>" % (curve.name,), repr(curve))
+
+
+ def test_to_EC_KEY(self):
+ """
+ The curve object can export a version of itself as an EC_KEY* via the
+ private :py:meth:`_EllipticCurve._to_EC_KEY`.
+ """
+ curves = get_elliptic_curves()
+ if curves:
+ curve = next(iter(curves))
+ # It's not easy to assert anything about this object. However, see
+ # leakcheck/crypto.py for a test that demonstrates it at least does
+ # not leak memory.
+ curve._to_EC_KEY()
+
+
+
+class EllipticCurveFactory(object):
+ """
+ A helper to get the names of two curves.
+ """
+ def __init__(self):
+ curves = iter(get_elliptic_curves())
+ try:
+ self.curve_name = next(curves).name
+ self.another_curve_name = next(curves).name
+ except StopIteration:
+ self.curve_name = self.another_curve_name = None
+
+
+
+class EllipticCurveEqualityTests(TestCase, EqualityTestsMixin):
+ """
+ Tests :py:type:`_EllipticCurve`\ 's implementation of ``==`` and ``!=``.
+ """
+ curve_factory = EllipticCurveFactory()
+
+ if curve_factory.curve_name is None:
+ skip = "There are no curves available there can be no curve objects."
+
+
+ def anInstance(self):
+ """
+ Get the curve object for an arbitrary curve supported by the system.
+ """
+ return get_elliptic_curve(self.curve_factory.curve_name)
+
+
+ def anotherInstance(self):
+ """
+ Get the curve object for an arbitrary curve supported by the system -
+ but not the one returned by C{anInstance}.
+ """
+ return get_elliptic_curve(self.curve_factory.another_curve_name)
+
+
+
+class EllipticCurveHashTests(TestCase):
+ """
+ Tests for :py:type:`_EllipticCurve`\ 's implementation of hashing (thus use
+ as an item in a :py:type:`dict` or :py:type:`set`).
+ """
+ curve_factory = EllipticCurveFactory()
+
+ if curve_factory.curve_name is None:
+ skip = "There are no curves available there can be no curve objects."
+
+
+ def test_contains(self):
+ """
+ The ``in`` operator reports that a :py:type:`set` containing a curve
+ does contain that curve.
+ """
+ curve = get_elliptic_curve(self.curve_factory.curve_name)
+ curves = set([curve])
+ self.assertIn(curve, curves)
+
+
+ def test_does_not_contain(self):
+ """
+ The ``in`` operator reports that a :py:type:`set` not containing a
+ curve does not contain that curve.
+ """
+ curve = get_elliptic_curve(self.curve_factory.curve_name)
+ curves = set([get_elliptic_curve(self.curve_factory.another_curve_name)])
+ self.assertNotIn(curve, curves)
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/Python/Lib/OpenSSL/test/test_rand.py b/lib/Python/Lib/OpenSSL/test/test_rand.py
new file mode 100644
index 000000000..3d5c2906d
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/test_rand.py
@@ -0,0 +1,223 @@
+# Copyright (c) Frederick Dean
+# See LICENSE for details.
+
+"""
+Unit tests for :py:obj:`OpenSSL.rand`.
+"""
+
+from unittest import main
+import os
+import stat
+import sys
+
+from OpenSSL.test.util import NON_ASCII, TestCase, b
+from OpenSSL import rand
+
+
+class RandTests(TestCase):
+ def test_bytes_wrong_args(self):
+ """
+ :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or with a non-:py:obj:`int` argument.
+ """
+ self.assertRaises(TypeError, rand.bytes)
+ self.assertRaises(TypeError, rand.bytes, None)
+ self.assertRaises(TypeError, rand.bytes, 3, None)
+
+
+ def test_insufficientMemory(self):
+ """
+ :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`MemoryError` if more bytes
+ are requested than will fit in memory.
+ """
+ self.assertRaises(MemoryError, rand.bytes, sys.maxsize)
+
+
+ def test_bytes(self):
+ """
+ Verify that we can obtain bytes from rand_bytes() and
+ that they are different each time. Test the parameter
+ of rand_bytes() for bad values.
+ """
+ b1 = rand.bytes(50)
+ self.assertEqual(len(b1), 50)
+ b2 = rand.bytes(num_bytes=50) # parameter by name
+ self.assertNotEqual(b1, b2) # Hip, Hip, Horay! FIPS complaince
+ b3 = rand.bytes(num_bytes=0)
+ self.assertEqual(len(b3), 0)
+ exc = self.assertRaises(ValueError, rand.bytes, -1)
+ self.assertEqual(str(exc), "num_bytes must not be negative")
+
+
+ def test_add_wrong_args(self):
+ """
+ When called with the wrong number of arguments, or with arguments not of
+ type :py:obj:`str` and :py:obj:`int`, :py:obj:`OpenSSL.rand.add` raises :py:obj:`TypeError`.
+ """
+ self.assertRaises(TypeError, rand.add)
+ self.assertRaises(TypeError, rand.add, b("foo"), None)
+ self.assertRaises(TypeError, rand.add, None, 3)
+ self.assertRaises(TypeError, rand.add, b("foo"), 3, None)
+
+
+ def test_add(self):
+ """
+ :py:obj:`OpenSSL.rand.add` adds entropy to the PRNG.
+ """
+ rand.add(b('hamburger'), 3)
+
+
+ def test_seed_wrong_args(self):
+ """
+ When called with the wrong number of arguments, or with a non-:py:obj:`str`
+ argument, :py:obj:`OpenSSL.rand.seed` raises :py:obj:`TypeError`.
+ """
+ self.assertRaises(TypeError, rand.seed)
+ self.assertRaises(TypeError, rand.seed, None)
+ self.assertRaises(TypeError, rand.seed, b("foo"), None)
+
+
+ def test_seed(self):
+ """
+ :py:obj:`OpenSSL.rand.seed` adds entropy to the PRNG.
+ """
+ rand.seed(b('milk shake'))
+
+
+ def test_status_wrong_args(self):
+ """
+ :py:obj:`OpenSSL.rand.status` raises :py:obj:`TypeError` when called with any
+ arguments.
+ """
+ self.assertRaises(TypeError, rand.status, None)
+
+
+ def test_status(self):
+ """
+ :py:obj:`OpenSSL.rand.status` returns :py:obj:`True` if the PRNG has sufficient
+ entropy, :py:obj:`False` otherwise.
+ """
+ # It's hard to know what it is actually going to return. Different
+ # OpenSSL random engines decide differently whether they have enough
+ # entropy or not.
+ self.assertTrue(rand.status() in (1, 2))
+
+
+ def test_egd_wrong_args(self):
+ """
+ :py:obj:`OpenSSL.rand.egd` raises :py:obj:`TypeError` when called with the wrong
+ number of arguments or with arguments not of type :py:obj:`str` and :py:obj:`int`.
+ """
+ self.assertRaises(TypeError, rand.egd)
+ self.assertRaises(TypeError, rand.egd, None)
+ self.assertRaises(TypeError, rand.egd, "foo", None)
+ self.assertRaises(TypeError, rand.egd, None, 3)
+ self.assertRaises(TypeError, rand.egd, "foo", 3, None)
+
+
+ def test_egd_missing(self):
+ """
+ :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the
+ EGD socket passed to it does not exist.
+ """
+ result = rand.egd(self.mktemp())
+ expected = (-1, 0)
+ self.assertTrue(
+ result in expected,
+ "%r not in %r" % (result, expected))
+
+
+ def test_egd_missing_and_bytes(self):
+ """
+ :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the
+ EGD socket passed to it does not exist even if a size argument is
+ explicitly passed.
+ """
+ result = rand.egd(self.mktemp(), 1024)
+ expected = (-1, 0)
+ self.assertTrue(
+ result in expected,
+ "%r not in %r" % (result, expected))
+
+
+ def test_cleanup_wrong_args(self):
+ """
+ :py:obj:`OpenSSL.rand.cleanup` raises :py:obj:`TypeError` when called with any
+ arguments.
+ """
+ self.assertRaises(TypeError, rand.cleanup, None)
+
+
+ def test_cleanup(self):
+ """
+ :py:obj:`OpenSSL.rand.cleanup` releases the memory used by the PRNG and returns
+ :py:obj:`None`.
+ """
+ self.assertIdentical(rand.cleanup(), None)
+
+
+ def test_load_file_wrong_args(self):
+ """
+ :py:obj:`OpenSSL.rand.load_file` raises :py:obj:`TypeError` when called the wrong
+ number of arguments or arguments not of type :py:obj:`str` and :py:obj:`int`.
+ """
+ self.assertRaises(TypeError, rand.load_file)
+ self.assertRaises(TypeError, rand.load_file, "foo", None)
+ self.assertRaises(TypeError, rand.load_file, None, 1)
+ self.assertRaises(TypeError, rand.load_file, "foo", 1, None)
+
+
+ def test_write_file_wrong_args(self):
+ """
+ :py:obj:`OpenSSL.rand.write_file` raises :py:obj:`TypeError` when called with the
+ wrong number of arguments or a non-:py:obj:`str` argument.
+ """
+ self.assertRaises(TypeError, rand.write_file)
+ self.assertRaises(TypeError, rand.write_file, None)
+ self.assertRaises(TypeError, rand.write_file, "foo", None)
+
+ def _read_write_test(self, path):
+ """
+ Verify that ``rand.write_file`` and ``rand.load_file`` can be used.
+ """
+ # Create the file so cleanup is more straightforward
+ with open(path, "w"):
+ pass
+
+ try:
+ # Write random bytes to a file
+ rand.write_file(path)
+
+ # Verify length of written file
+ size = os.stat(path)[stat.ST_SIZE]
+ self.assertEqual(1024, size)
+
+ # Read random bytes from file
+ rand.load_file(path)
+ rand.load_file(path, 4) # specify a length
+ finally:
+ # Cleanup
+ os.unlink(path)
+
+
+ def test_bytes_paths(self):
+ """
+ Random data can be saved and loaded to files with paths specified as
+ bytes.
+ """
+ path = self.mktemp()
+ path += NON_ASCII.encode(sys.getfilesystemencoding())
+ self._read_write_test(path)
+
+
+ def test_unicode_paths(self):
+ """
+ Random data can be saved and loaded to files with paths specified as
+ unicode.
+ """
+ path = self.mktemp().decode('utf-8') + NON_ASCII
+ self._read_write_test(path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/Python/Lib/OpenSSL/test/test_ssl.py b/lib/Python/Lib/OpenSSL/test/test_ssl.py
new file mode 100644
index 000000000..bb1c9aedf
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/test_ssl.py
@@ -0,0 +1,3775 @@
+# Copyright (C) Jean-Paul Calderone
+# See LICENSE for details.
+
+"""
+Unit tests for :py:obj:`OpenSSL.SSL`.
+"""
+
+from gc import collect, get_referrers
+from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK, EPIPE, ESHUTDOWN
+from sys import platform, getfilesystemencoding
+from socket import SHUT_RDWR, error, socket
+from os import makedirs
+from os.path import join
+from unittest import main
+from weakref import ref
+from warnings import catch_warnings, simplefilter
+
+from six import PY3, text_type, u
+
+from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM
+from OpenSSL.crypto import PKey, X509, X509Extension, X509Store
+from OpenSSL.crypto import dump_privatekey, load_privatekey
+from OpenSSL.crypto import dump_certificate, load_certificate
+from OpenSSL.crypto import get_elliptic_curves
+
+from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS
+from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON
+from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN
+from OpenSSL.SSL import (
+ SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD,
+ TLSv1_1_METHOD, TLSv1_2_METHOD)
+from OpenSSL.SSL import OP_SINGLE_DH_USE, OP_NO_SSLv2, OP_NO_SSLv3
+from OpenSSL.SSL import (
+ VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, VERIFY_NONE)
+
+from OpenSSL.SSL import (
+ SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH,
+ SESS_CACHE_NO_AUTO_CLEAR, SESS_CACHE_NO_INTERNAL_LOOKUP,
+ SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_NO_INTERNAL)
+
+from OpenSSL.SSL import (
+ Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError)
+from OpenSSL.SSL import (
+ Context, ContextType, Session, Connection, ConnectionType, SSLeay_version)
+
+from OpenSSL._util import lib as _lib
+
+from OpenSSL.test.util import WARNING_TYPE_EXPECTED, NON_ASCII, TestCase, b
+from OpenSSL.test.test_crypto import (
+ cleartextCertificatePEM, cleartextPrivateKeyPEM,
+ client_cert_pem, client_key_pem, server_cert_pem, server_key_pem,
+ root_cert_pem)
+
+try:
+ from OpenSSL.SSL import OP_NO_QUERY_MTU
+except ImportError:
+ OP_NO_QUERY_MTU = None
+try:
+ from OpenSSL.SSL import OP_COOKIE_EXCHANGE
+except ImportError:
+ OP_COOKIE_EXCHANGE = None
+try:
+ from OpenSSL.SSL import OP_NO_TICKET
+except ImportError:
+ OP_NO_TICKET = None
+
+try:
+ from OpenSSL.SSL import OP_NO_COMPRESSION
+except ImportError:
+ OP_NO_COMPRESSION = None
+
+try:
+ from OpenSSL.SSL import MODE_RELEASE_BUFFERS
+except ImportError:
+ MODE_RELEASE_BUFFERS = None
+
+try:
+ from OpenSSL.SSL import OP_NO_TLSv1, OP_NO_TLSv1_1, OP_NO_TLSv1_2
+except ImportError:
+ OP_NO_TLSv1 = OP_NO_TLSv1_1 = OP_NO_TLSv1_2 = None
+
+from OpenSSL.SSL import (
+ SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_ST_INIT, SSL_ST_BEFORE,
+ SSL_ST_OK, SSL_ST_RENEGOTIATE,
+ SSL_CB_LOOP, SSL_CB_EXIT, SSL_CB_READ, SSL_CB_WRITE, SSL_CB_ALERT,
+ SSL_CB_READ_ALERT, SSL_CB_WRITE_ALERT, SSL_CB_ACCEPT_LOOP,
+ SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_CONNECT_EXIT,
+ SSL_CB_HANDSHAKE_START, SSL_CB_HANDSHAKE_DONE)
+
+# openssl dhparam 128 -out dh-128.pem (note that 128 is a small number of bits
+# to use)
+dhparam = """\
+-----BEGIN DH PARAMETERS-----
+MBYCEQCobsg29c9WZP/54oAPcwiDAgEC
+-----END DH PARAMETERS-----
+"""
+
+
+def join_bytes_or_unicode(prefix, suffix):
+ """
+ Join two path components of either ``bytes`` or ``unicode``.
+
+ The return type is the same as the type of ``prefix``.
+ """
+ # If the types are the same, nothing special is necessary.
+ if type(prefix) == type(suffix):
+ return join(prefix, suffix)
+
+ # Otherwise, coerce suffix to the type of prefix.
+ if isinstance(prefix, text_type):
+ return join(prefix, suffix.decode(getfilesystemencoding()))
+ else:
+ return join(prefix, suffix.encode(getfilesystemencoding()))
+
+
+def verify_cb(conn, cert, errnum, depth, ok):
+ return ok
+
+
+def socket_pair():
+ """
+ Establish and return a pair of network sockets connected to each other.
+ """
+ # Connect a pair of sockets
+ port = socket()
+ port.bind(('', 0))
+ port.listen(1)
+ client = socket()
+ client.setblocking(False)
+ client.connect_ex(("127.0.0.1", port.getsockname()[1]))
+ client.setblocking(True)
+ server = port.accept()[0]
+
+ # Let's pass some unencrypted data to make sure our socket connection is
+ # fine. Just one byte, so we don't have to worry about buffers getting
+ # filled up or fragmentation.
+ server.send(b("x"))
+ assert client.recv(1024) == b("x")
+ client.send(b("y"))
+ assert server.recv(1024) == b("y")
+
+ # Most of our callers want non-blocking sockets, make it easy for them.
+ server.setblocking(False)
+ client.setblocking(False)
+
+ return (server, client)
+
+
+
+def handshake(client, server):
+ conns = [client, server]
+ while conns:
+ for conn in conns:
+ try:
+ conn.do_handshake()
+ except WantReadError:
+ pass
+ else:
+ conns.remove(conn)
+
+
+def _create_certificate_chain():
+ """
+ Construct and return a chain of certificates.
+
+ 1. A new self-signed certificate authority certificate (cacert)
+ 2. A new intermediate certificate signed by cacert (icert)
+ 3. A new server certificate signed by icert (scert)
+ """
+ caext = X509Extension(b('basicConstraints'), False, b('CA:true'))
+
+ # Step 1
+ cakey = PKey()
+ cakey.generate_key(TYPE_RSA, 512)
+ cacert = X509()
+ cacert.get_subject().commonName = "Authority Certificate"
+ cacert.set_issuer(cacert.get_subject())
+ cacert.set_pubkey(cakey)
+ cacert.set_notBefore(b("20000101000000Z"))
+ cacert.set_notAfter(b("20200101000000Z"))
+ cacert.add_extensions([caext])
+ cacert.set_serial_number(0)
+ cacert.sign(cakey, "sha1")
+
+ # Step 2
+ ikey = PKey()
+ ikey.generate_key(TYPE_RSA, 512)
+ icert = X509()
+ icert.get_subject().commonName = "Intermediate Certificate"
+ icert.set_issuer(cacert.get_subject())
+ icert.set_pubkey(ikey)
+ icert.set_notBefore(b("20000101000000Z"))
+ icert.set_notAfter(b("20200101000000Z"))
+ icert.add_extensions([caext])
+ icert.set_serial_number(0)
+ icert.sign(cakey, "sha1")
+
+ # Step 3
+ skey = PKey()
+ skey.generate_key(TYPE_RSA, 512)
+ scert = X509()
+ scert.get_subject().commonName = "Server Certificate"
+ scert.set_issuer(icert.get_subject())
+ scert.set_pubkey(skey)
+ scert.set_notBefore(b("20000101000000Z"))
+ scert.set_notAfter(b("20200101000000Z"))
+ scert.add_extensions([
+ X509Extension(b('basicConstraints'), True, b('CA:false'))])
+ scert.set_serial_number(0)
+ scert.sign(ikey, "sha1")
+
+ return [(cakey, cacert), (ikey, icert), (skey, scert)]
+
+
+
+class _LoopbackMixin:
+ """
+ Helper mixin which defines methods for creating a connected socket pair and
+ for forcing two connected SSL sockets to talk to each other via memory BIOs.
+ """
+ def _loopbackClientFactory(self, socket):
+ client = Connection(Context(TLSv1_METHOD), socket)
+ client.set_connect_state()
+ return client
+
+
+ def _loopbackServerFactory(self, socket):
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+ server = Connection(ctx, socket)
+ server.set_accept_state()
+ return server
+
+
+ def _loopback(self, serverFactory=None, clientFactory=None):
+ if serverFactory is None:
+ serverFactory = self._loopbackServerFactory
+ if clientFactory is None:
+ clientFactory = self._loopbackClientFactory
+
+ (server, client) = socket_pair()
+ server = serverFactory(server)
+ client = clientFactory(client)
+
+ handshake(client, server)
+
+ server.setblocking(True)
+ client.setblocking(True)
+ return server, client
+
+
+ def _interactInMemory(self, client_conn, server_conn):
+ """
+ Try to read application bytes from each of the two :py:obj:`Connection`
+ objects. Copy bytes back and forth between their send/receive buffers
+ for as long as there is anything to copy. When there is nothing more
+ to copy, return :py:obj:`None`. If one of them actually manages to deliver
+ some application bytes, return a two-tuple of the connection from which
+ the bytes were read and the bytes themselves.
+ """
+ wrote = True
+ while wrote:
+ # Loop until neither side has anything to say
+ wrote = False
+
+ # Copy stuff from each side's send buffer to the other side's
+ # receive buffer.
+ for (read, write) in [(client_conn, server_conn),
+ (server_conn, client_conn)]:
+
+ # Give the side a chance to generate some more bytes, or
+ # succeed.
+ try:
+ data = read.recv(2 ** 16)
+ except WantReadError:
+ # It didn't succeed, so we'll hope it generated some
+ # output.
+ pass
+ else:
+ # It did succeed, so we'll stop now and let the caller deal
+ # with it.
+ return (read, data)
+
+ while True:
+ # Keep copying as long as there's more stuff there.
+ try:
+ dirty = read.bio_read(4096)
+ except WantReadError:
+ # Okay, nothing more waiting to be sent. Stop
+ # processing this send buffer.
+ break
+ else:
+ # Keep track of the fact that someone generated some
+ # output.
+ wrote = True
+ write.bio_write(dirty)
+
+
+ def _handshakeInMemory(self, client_conn, server_conn):
+ """
+ Perform the TLS handshake between two :py:class:`Connection` instances
+ connected to each other via memory BIOs.
+ """
+ client_conn.set_connect_state()
+ server_conn.set_accept_state()
+
+ for conn in [client_conn, server_conn]:
+ try:
+ conn.do_handshake()
+ except WantReadError:
+ pass
+
+ self._interactInMemory(client_conn, server_conn)
+
+
+
+class VersionTests(TestCase):
+ """
+ Tests for version information exposed by
+ :py:obj:`OpenSSL.SSL.SSLeay_version` and
+ :py:obj:`OpenSSL.SSL.OPENSSL_VERSION_NUMBER`.
+ """
+ def test_OPENSSL_VERSION_NUMBER(self):
+ """
+ :py:obj:`OPENSSL_VERSION_NUMBER` is an integer with status in the low
+ byte and the patch, fix, minor, and major versions in the
+ nibbles above that.
+ """
+ self.assertTrue(isinstance(OPENSSL_VERSION_NUMBER, int))
+
+
+ def test_SSLeay_version(self):
+ """
+ :py:obj:`SSLeay_version` takes a version type indicator and returns
+ one of a number of version strings based on that indicator.
+ """
+ versions = {}
+ for t in [SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON,
+ SSLEAY_PLATFORM, SSLEAY_DIR]:
+ version = SSLeay_version(t)
+ versions[version] = t
+ self.assertTrue(isinstance(version, bytes))
+ self.assertEqual(len(versions), 5)
+
+
+
+class ContextTests(TestCase, _LoopbackMixin):
+ """
+ Unit tests for :py:obj:`OpenSSL.SSL.Context`.
+ """
+ def test_method(self):
+ """
+ :py:obj:`Context` can be instantiated with one of :py:obj:`SSLv2_METHOD`,
+ :py:obj:`SSLv3_METHOD`, :py:obj:`SSLv23_METHOD`, :py:obj:`TLSv1_METHOD`,
+ :py:obj:`TLSv1_1_METHOD`, or :py:obj:`TLSv1_2_METHOD`.
+ """
+ methods = [
+ SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD]
+ for meth in methods:
+ Context(meth)
+
+
+ maybe = [SSLv2_METHOD, TLSv1_1_METHOD, TLSv1_2_METHOD]
+ for meth in maybe:
+ try:
+ Context(meth)
+ except (Error, ValueError):
+ # Some versions of OpenSSL have SSLv2 / TLSv1.1 / TLSv1.2, some
+ # don't. Difficult to say in advance.
+ pass
+
+ self.assertRaises(TypeError, Context, "")
+ self.assertRaises(ValueError, Context, 10)
+
+
+ if not PY3:
+ def test_method_long(self):
+ """
+ On Python 2 :py:class:`Context` accepts values of type
+ :py:obj:`long` as well as :py:obj:`int`.
+ """
+ Context(long(TLSv1_METHOD))
+
+
+
+ def test_type(self):
+ """
+ :py:obj:`Context` and :py:obj:`ContextType` refer to the same type object and can be
+ used to create instances of that type.
+ """
+ self.assertIdentical(Context, ContextType)
+ self.assertConsistentType(Context, 'Context', TLSv1_METHOD)
+
+
+ def test_use_privatekey(self):
+ """
+ :py:obj:`Context.use_privatekey` takes an :py:obj:`OpenSSL.crypto.PKey` instance.
+ """
+ key = PKey()
+ key.generate_key(TYPE_RSA, 128)
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_privatekey(key)
+ self.assertRaises(TypeError, ctx.use_privatekey, "")
+
+
+ def test_use_privatekey_file_missing(self):
+ """
+ :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error`
+ when passed the name of a file which does not exist.
+ """
+ ctx = Context(TLSv1_METHOD)
+ self.assertRaises(Error, ctx.use_privatekey_file, self.mktemp())
+
+
+ def _use_privatekey_file_test(self, pemfile, filetype):
+ """
+ Verify that calling ``Context.use_privatekey_file`` with the given
+ arguments does not raise an exception.
+ """
+ key = PKey()
+ key.generate_key(TYPE_RSA, 128)
+
+ with open(pemfile, "wt") as pem:
+ pem.write(
+ dump_privatekey(FILETYPE_PEM, key).decode("ascii")
+ )
+
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_privatekey_file(pemfile, filetype)
+
+
+ def test_use_privatekey_file_bytes(self):
+ """
+ A private key can be specified from a file by passing a ``bytes``
+ instance giving the file name to ``Context.use_privatekey_file``.
+ """
+ self._use_privatekey_file_test(
+ self.mktemp() + NON_ASCII.encode(getfilesystemencoding()),
+ FILETYPE_PEM,
+ )
+
+
+ def test_use_privatekey_file_unicode(self):
+ """
+ A private key can be specified from a file by passing a ``unicode``
+ instance giving the file name to ``Context.use_privatekey_file``.
+ """
+ self._use_privatekey_file_test(
+ self.mktemp().decode(getfilesystemencoding()) + NON_ASCII,
+ FILETYPE_PEM,
+ )
+
+
+ if not PY3:
+ def test_use_privatekey_file_long(self):
+ """
+ On Python 2 :py:obj:`Context.use_privatekey_file` accepts a
+ filetype of type :py:obj:`long` as well as :py:obj:`int`.
+ """
+ self._use_privatekey_file_test(self.mktemp(), long(FILETYPE_PEM))
+
+
+ def test_use_certificate_wrong_args(self):
+ """
+ :py:obj:`Context.use_certificate_wrong_args` raises :py:obj:`TypeError`
+ when not passed exactly one :py:obj:`OpenSSL.crypto.X509` instance as an
+ argument.
+ """
+ ctx = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, ctx.use_certificate)
+ self.assertRaises(TypeError, ctx.use_certificate, "hello, world")
+ self.assertRaises(TypeError, ctx.use_certificate, X509(), "hello, world")
+
+
+ def test_use_certificate_uninitialized(self):
+ """
+ :py:obj:`Context.use_certificate` raises :py:obj:`OpenSSL.SSL.Error`
+ when passed a :py:obj:`OpenSSL.crypto.X509` instance which has not been
+ initialized (ie, which does not actually have any certificate data).
+ """
+ ctx = Context(TLSv1_METHOD)
+ self.assertRaises(Error, ctx.use_certificate, X509())
+
+
+ def test_use_certificate(self):
+ """
+ :py:obj:`Context.use_certificate` sets the certificate which will be
+ used to identify connections created using the context.
+ """
+ # TODO
+ # Hard to assert anything. But we could set a privatekey then ask
+ # OpenSSL if the cert and key agree using check_privatekey. Then as
+ # long as check_privatekey works right we're good...
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_certificate(load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
+
+
+ def test_use_certificate_file_wrong_args(self):
+ """
+ :py:obj:`Context.use_certificate_file` raises :py:obj:`TypeError` if
+ called with zero arguments or more than two arguments, or if the first
+ argument is not a byte string or the second argumnent is not an integer.
+ """
+ ctx = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, ctx.use_certificate_file)
+ self.assertRaises(TypeError, ctx.use_certificate_file, b"somefile", object())
+ self.assertRaises(
+ TypeError, ctx.use_certificate_file, b"somefile", FILETYPE_PEM, object())
+ self.assertRaises(
+ TypeError, ctx.use_certificate_file, object(), FILETYPE_PEM)
+ self.assertRaises(
+ TypeError, ctx.use_certificate_file, b"somefile", object())
+
+
+ def test_use_certificate_file_missing(self):
+ """
+ :py:obj:`Context.use_certificate_file` raises
+ `:py:obj:`OpenSSL.SSL.Error` if passed the name of a file which does not
+ exist.
+ """
+ ctx = Context(TLSv1_METHOD)
+ self.assertRaises(Error, ctx.use_certificate_file, self.mktemp())
+
+
+ def _use_certificate_file_test(self, certificate_file):
+ """
+ Verify that calling ``Context.use_certificate_file`` with the given
+ filename doesn't raise an exception.
+ """
+ # TODO
+ # Hard to assert anything. But we could set a privatekey then ask
+ # OpenSSL if the cert and key agree using check_privatekey. Then as
+ # long as check_privatekey works right we're good...
+ with open(certificate_file, "wb") as pem_file:
+ pem_file.write(cleartextCertificatePEM)
+
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_certificate_file(certificate_file)
+
+
+ def test_use_certificate_file_bytes(self):
+ """
+ :py:obj:`Context.use_certificate_file` sets the certificate (given as a
+ ``bytes`` filename) which will be used to identify connections created
+ using the context.
+ """
+ filename = self.mktemp() + NON_ASCII.encode(getfilesystemencoding())
+ self._use_certificate_file_test(filename)
+
+
+ def test_use_certificate_file_unicode(self):
+ """
+ :py:obj:`Context.use_certificate_file` sets the certificate (given as a
+ ``bytes`` filename) which will be used to identify connections created
+ using the context.
+ """
+ filename = self.mktemp().decode(getfilesystemencoding()) + NON_ASCII
+ self._use_certificate_file_test(filename)
+
+
+ if not PY3:
+ def test_use_certificate_file_long(self):
+ """
+ On Python 2 :py:obj:`Context.use_certificate_file` accepts a
+ filetype of type :py:obj:`long` as well as :py:obj:`int`.
+ """
+ pem_filename = self.mktemp()
+ with open(pem_filename, "wb") as pem_file:
+ pem_file.write(cleartextCertificatePEM)
+
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_certificate_file(pem_filename, long(FILETYPE_PEM))
+
+
+ def test_check_privatekey_valid(self):
+ """
+ :py:obj:`Context.check_privatekey` returns :py:obj:`None` if the
+ :py:obj:`Context` instance has been configured to use a matched key and
+ certificate pair.
+ """
+ key = load_privatekey(FILETYPE_PEM, client_key_pem)
+ cert = load_certificate(FILETYPE_PEM, client_cert_pem)
+ context = Context(TLSv1_METHOD)
+ context.use_privatekey(key)
+ context.use_certificate(cert)
+ self.assertIs(None, context.check_privatekey())
+
+
+ def test_check_privatekey_invalid(self):
+ """
+ :py:obj:`Context.check_privatekey` raises :py:obj:`Error` if the
+ :py:obj:`Context` instance has been configured to use a key and
+ certificate pair which don't relate to each other.
+ """
+ key = load_privatekey(FILETYPE_PEM, client_key_pem)
+ cert = load_certificate(FILETYPE_PEM, server_cert_pem)
+ context = Context(TLSv1_METHOD)
+ context.use_privatekey(key)
+ context.use_certificate(cert)
+ self.assertRaises(Error, context.check_privatekey)
+
+
+ def test_check_privatekey_wrong_args(self):
+ """
+ :py:obj:`Context.check_privatekey` raises :py:obj:`TypeError` if called
+ with other than no arguments.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.check_privatekey, object())
+
+
+ def test_set_app_data_wrong_args(self):
+ """
+ :py:obj:`Context.set_app_data` raises :py:obj:`TypeError` if called with other than
+ one argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_app_data)
+ self.assertRaises(TypeError, context.set_app_data, None, None)
+
+
+ def test_get_app_data_wrong_args(self):
+ """
+ :py:obj:`Context.get_app_data` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.get_app_data, None)
+
+
+ def test_app_data(self):
+ """
+ :py:obj:`Context.set_app_data` stores an object for later retrieval using
+ :py:obj:`Context.get_app_data`.
+ """
+ app_data = object()
+ context = Context(TLSv1_METHOD)
+ context.set_app_data(app_data)
+ self.assertIdentical(context.get_app_data(), app_data)
+
+
+ def test_set_options_wrong_args(self):
+ """
+ :py:obj:`Context.set_options` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or a non-:py:obj:`int` argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_options)
+ self.assertRaises(TypeError, context.set_options, None)
+ self.assertRaises(TypeError, context.set_options, 1, None)
+
+
+ def test_set_options(self):
+ """
+ :py:obj:`Context.set_options` returns the new options value.
+ """
+ context = Context(TLSv1_METHOD)
+ options = context.set_options(OP_NO_SSLv2)
+ self.assertTrue(OP_NO_SSLv2 & options)
+
+
+ if not PY3:
+ def test_set_options_long(self):
+ """
+ On Python 2 :py:obj:`Context.set_options` accepts values of type
+ :py:obj:`long` as well as :py:obj:`int`.
+ """
+ context = Context(TLSv1_METHOD)
+ options = context.set_options(long(OP_NO_SSLv2))
+ self.assertTrue(OP_NO_SSLv2 & options)
+
+
+ def test_set_mode_wrong_args(self):
+ """
+ :py:obj:`Context.set`mode} raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or a non-:py:obj:`int` argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_mode)
+ self.assertRaises(TypeError, context.set_mode, None)
+ self.assertRaises(TypeError, context.set_mode, 1, None)
+
+
+ if MODE_RELEASE_BUFFERS is not None:
+ def test_set_mode(self):
+ """
+ :py:obj:`Context.set_mode` accepts a mode bitvector and returns the newly
+ set mode.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertTrue(
+ MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS))
+
+ if not PY3:
+ def test_set_mode_long(self):
+ """
+ On Python 2 :py:obj:`Context.set_mode` accepts values of type
+ :py:obj:`long` as well as :py:obj:`int`.
+ """
+ context = Context(TLSv1_METHOD)
+ mode = context.set_mode(long(MODE_RELEASE_BUFFERS))
+ self.assertTrue(MODE_RELEASE_BUFFERS & mode)
+ else:
+ "MODE_RELEASE_BUFFERS unavailable - OpenSSL version may be too old"
+
+
+ def test_set_timeout_wrong_args(self):
+ """
+ :py:obj:`Context.set_timeout` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or a non-:py:obj:`int` argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_timeout)
+ self.assertRaises(TypeError, context.set_timeout, None)
+ self.assertRaises(TypeError, context.set_timeout, 1, None)
+
+
+ def test_get_timeout_wrong_args(self):
+ """
+ :py:obj:`Context.get_timeout` raises :py:obj:`TypeError` if called with any arguments.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.get_timeout, None)
+
+
+ def test_timeout(self):
+ """
+ :py:obj:`Context.set_timeout` sets the session timeout for all connections
+ created using the context object. :py:obj:`Context.get_timeout` retrieves this
+ value.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_timeout(1234)
+ self.assertEquals(context.get_timeout(), 1234)
+
+
+ if not PY3:
+ def test_timeout_long(self):
+ """
+ On Python 2 :py:obj:`Context.set_timeout` accepts values of type
+ `long` as well as int.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_timeout(long(1234))
+ self.assertEquals(context.get_timeout(), 1234)
+
+
+ def test_set_verify_depth_wrong_args(self):
+ """
+ :py:obj:`Context.set_verify_depth` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or a non-:py:obj:`int` argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_verify_depth)
+ self.assertRaises(TypeError, context.set_verify_depth, None)
+ self.assertRaises(TypeError, context.set_verify_depth, 1, None)
+
+
+ def test_get_verify_depth_wrong_args(self):
+ """
+ :py:obj:`Context.get_verify_depth` raises :py:obj:`TypeError` if called with any arguments.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.get_verify_depth, None)
+
+
+ def test_verify_depth(self):
+ """
+ :py:obj:`Context.set_verify_depth` sets the number of certificates in a chain
+ to follow before giving up. The value can be retrieved with
+ :py:obj:`Context.get_verify_depth`.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_verify_depth(11)
+ self.assertEquals(context.get_verify_depth(), 11)
+
+
+ if not PY3:
+ def test_verify_depth_long(self):
+ """
+ On Python 2 :py:obj:`Context.set_verify_depth` accepts values of
+ type `long` as well as int.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_verify_depth(long(11))
+ self.assertEquals(context.get_verify_depth(), 11)
+
+
+ def _write_encrypted_pem(self, passphrase):
+ """
+ Write a new private key out to a new file, encrypted using the given
+ passphrase. Return the path to the new file.
+ """
+ key = PKey()
+ key.generate_key(TYPE_RSA, 128)
+ pemFile = self.mktemp()
+ fObj = open(pemFile, 'w')
+ pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase)
+ fObj.write(pem.decode('ascii'))
+ fObj.close()
+ return pemFile
+
+
+ def test_set_passwd_cb_wrong_args(self):
+ """
+ :py:obj:`Context.set_passwd_cb` raises :py:obj:`TypeError` if called with the
+ wrong arguments or with a non-callable first argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_passwd_cb)
+ self.assertRaises(TypeError, context.set_passwd_cb, None)
+ self.assertRaises(TypeError, context.set_passwd_cb, lambda: None, None, None)
+
+
+ def test_set_passwd_cb(self):
+ """
+ :py:obj:`Context.set_passwd_cb` accepts a callable which will be invoked when
+ a private key is loaded from an encrypted PEM.
+ """
+ passphrase = b("foobar")
+ pemFile = self._write_encrypted_pem(passphrase)
+ calledWith = []
+ def passphraseCallback(maxlen, verify, extra):
+ calledWith.append((maxlen, verify, extra))
+ return passphrase
+ context = Context(TLSv1_METHOD)
+ context.set_passwd_cb(passphraseCallback)
+ context.use_privatekey_file(pemFile)
+ self.assertTrue(len(calledWith), 1)
+ self.assertTrue(isinstance(calledWith[0][0], int))
+ self.assertTrue(isinstance(calledWith[0][1], int))
+ self.assertEqual(calledWith[0][2], None)
+
+
+ def test_passwd_callback_exception(self):
+ """
+ :py:obj:`Context.use_privatekey_file` propagates any exception raised by the
+ passphrase callback.
+ """
+ pemFile = self._write_encrypted_pem(b("monkeys are nice"))
+ def passphraseCallback(maxlen, verify, extra):
+ raise RuntimeError("Sorry, I am a fail.")
+
+ context = Context(TLSv1_METHOD)
+ context.set_passwd_cb(passphraseCallback)
+ self.assertRaises(RuntimeError, context.use_privatekey_file, pemFile)
+
+
+ def test_passwd_callback_false(self):
+ """
+ :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the
+ passphrase callback returns a false value.
+ """
+ pemFile = self._write_encrypted_pem(b("monkeys are nice"))
+ def passphraseCallback(maxlen, verify, extra):
+ return b""
+
+ context = Context(TLSv1_METHOD)
+ context.set_passwd_cb(passphraseCallback)
+ self.assertRaises(Error, context.use_privatekey_file, pemFile)
+
+
+ def test_passwd_callback_non_string(self):
+ """
+ :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the
+ passphrase callback returns a true non-string value.
+ """
+ pemFile = self._write_encrypted_pem(b("monkeys are nice"))
+ def passphraseCallback(maxlen, verify, extra):
+ return 10
+
+ context = Context(TLSv1_METHOD)
+ context.set_passwd_cb(passphraseCallback)
+ self.assertRaises(ValueError, context.use_privatekey_file, pemFile)
+
+
+ def test_passwd_callback_too_long(self):
+ """
+ If the passphrase returned by the passphrase callback returns a string
+ longer than the indicated maximum length, it is truncated.
+ """
+ # A priori knowledge!
+ passphrase = b("x") * 1024
+ pemFile = self._write_encrypted_pem(passphrase)
+ def passphraseCallback(maxlen, verify, extra):
+ assert maxlen == 1024
+ return passphrase + b("y")
+
+ context = Context(TLSv1_METHOD)
+ context.set_passwd_cb(passphraseCallback)
+ # This shall succeed because the truncated result is the correct
+ # passphrase.
+ context.use_privatekey_file(pemFile)
+
+
+ def test_set_info_callback(self):
+ """
+ :py:obj:`Context.set_info_callback` accepts a callable which will be invoked
+ when certain information about an SSL connection is available.
+ """
+ (server, client) = socket_pair()
+
+ clientSSL = Connection(Context(TLSv1_METHOD), client)
+ clientSSL.set_connect_state()
+
+ called = []
+ def info(conn, where, ret):
+ called.append((conn, where, ret))
+ context = Context(TLSv1_METHOD)
+ context.set_info_callback(info)
+ context.use_certificate(
+ load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
+ context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
+
+ serverSSL = Connection(context, server)
+ serverSSL.set_accept_state()
+
+ handshake(clientSSL, serverSSL)
+
+ # The callback must always be called with a Connection instance as the
+ # first argument. It would probably be better to split this into
+ # separate tests for client and server side info callbacks so we could
+ # assert it is called with the right Connection instance. It would
+ # also be good to assert *something* about `where` and `ret`.
+ notConnections = [
+ conn for (conn, where, ret) in called
+ if not isinstance(conn, Connection)]
+ self.assertEqual(
+ [], notConnections,
+ "Some info callback arguments were not Connection instaces.")
+
+
+ def _load_verify_locations_test(self, *args):
+ """
+ Create a client context which will verify the peer certificate and call
+ its :py:obj:`load_verify_locations` method with the given arguments.
+ Then connect it to a server and ensure that the handshake succeeds.
+ """
+ (server, client) = socket_pair()
+
+ clientContext = Context(TLSv1_METHOD)
+ clientContext.load_verify_locations(*args)
+ # Require that the server certificate verify properly or the
+ # connection will fail.
+ clientContext.set_verify(
+ VERIFY_PEER,
+ lambda conn, cert, errno, depth, preverify_ok: preverify_ok)
+
+ clientSSL = Connection(clientContext, client)
+ clientSSL.set_connect_state()
+
+ serverContext = Context(TLSv1_METHOD)
+ serverContext.use_certificate(
+ load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
+ serverContext.use_privatekey(
+ load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
+
+ serverSSL = Connection(serverContext, server)
+ serverSSL.set_accept_state()
+
+ # Without load_verify_locations above, the handshake
+ # will fail:
+ # Error: [('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE',
+ # 'certificate verify failed')]
+ handshake(clientSSL, serverSSL)
+
+ cert = clientSSL.get_peer_certificate()
+ self.assertEqual(cert.get_subject().CN, 'Testing Root CA')
+
+
+ def _load_verify_cafile(self, cafile):
+ """
+ Verify that if path to a file containing a certificate is passed to
+ ``Context.load_verify_locations`` for the ``cafile`` parameter, that
+ certificate is used as a trust root for the purposes of verifying
+ connections created using that ``Context``.
+ """
+ fObj = open(cafile, 'w')
+ fObj.write(cleartextCertificatePEM.decode('ascii'))
+ fObj.close()
+
+ self._load_verify_locations_test(cafile)
+
+
+ def test_load_verify_bytes_cafile(self):
+ """
+ :py:obj:`Context.load_verify_locations` accepts a file name as a
+ ``bytes`` instance and uses the certificates within for verification
+ purposes.
+ """
+ cafile = self.mktemp() + NON_ASCII.encode(getfilesystemencoding())
+ self._load_verify_cafile(cafile)
+
+
+ def test_load_verify_unicode_cafile(self):
+ """
+ :py:obj:`Context.load_verify_locations` accepts a file name as a
+ ``unicode`` instance and uses the certificates within for verification
+ purposes.
+ """
+ self._load_verify_cafile(
+ self.mktemp().decode(getfilesystemencoding()) + NON_ASCII
+ )
+
+
+ def test_load_verify_invalid_file(self):
+ """
+ :py:obj:`Context.load_verify_locations` raises :py:obj:`Error` when passed a
+ non-existent cafile.
+ """
+ clientContext = Context(TLSv1_METHOD)
+ self.assertRaises(
+ Error, clientContext.load_verify_locations, self.mktemp())
+
+
+ def _load_verify_directory_locations_capath(self, capath):
+ """
+ Verify that if path to a directory containing certificate files is
+ passed to ``Context.load_verify_locations`` for the ``capath``
+ parameter, those certificates are used as trust roots for the purposes
+ of verifying connections created using that ``Context``.
+ """
+ makedirs(capath)
+ # Hash values computed manually with c_rehash to avoid depending on
+ # c_rehash in the test suite. One is from OpenSSL 0.9.8, the other
+ # from OpenSSL 1.0.0.
+ for name in [b'c7adac82.0', b'c3705638.0']:
+ cafile = join_bytes_or_unicode(capath, name)
+ with open(cafile, 'w') as fObj:
+ fObj.write(cleartextCertificatePEM.decode('ascii'))
+
+ self._load_verify_locations_test(None, capath)
+
+
+ def test_load_verify_directory_bytes_capath(self):
+ """
+ :py:obj:`Context.load_verify_locations` accepts a directory name as a
+ ``bytes`` instance and uses the certificates within for verification
+ purposes.
+ """
+ self._load_verify_directory_locations_capath(
+ self.mktemp() + NON_ASCII.encode(getfilesystemencoding())
+ )
+
+
+ def test_load_verify_directory_unicode_capath(self):
+ """
+ :py:obj:`Context.load_verify_locations` accepts a directory name as a
+ ``unicode`` instance and uses the certificates within for verification
+ purposes.
+ """
+ self._load_verify_directory_locations_capath(
+ self.mktemp().decode(getfilesystemencoding()) + NON_ASCII
+ )
+
+
+ def test_load_verify_locations_wrong_args(self):
+ """
+ :py:obj:`Context.load_verify_locations` raises :py:obj:`TypeError` if called with
+ the wrong number of arguments or with non-:py:obj:`str` arguments.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.load_verify_locations)
+ self.assertRaises(TypeError, context.load_verify_locations, object())
+ self.assertRaises(TypeError, context.load_verify_locations, object(), object())
+ self.assertRaises(TypeError, context.load_verify_locations, None, None, None)
+
+
+ if platform == "win32":
+ "set_default_verify_paths appears not to work on Windows. "
+ "See LP#404343 and LP#404344."
+ else:
+ def test_set_default_verify_paths(self):
+ """
+ :py:obj:`Context.set_default_verify_paths` causes the platform-specific CA
+ certificate locations to be used for verification purposes.
+ """
+ # Testing this requires a server with a certificate signed by one of
+ # the CAs in the platform CA location. Getting one of those costs
+ # money. Fortunately (or unfortunately, depending on your
+ # perspective), it's easy to think of a public server on the
+ # internet which has such a certificate. Connecting to the network
+ # in a unit test is bad, but it's the only way I can think of to
+ # really test this. -exarkun
+
+ # Arg, verisign.com doesn't speak anything newer than TLS 1.0
+ context = Context(TLSv1_METHOD)
+ context.set_default_verify_paths()
+ context.set_verify(
+ VERIFY_PEER,
+ lambda conn, cert, errno, depth, preverify_ok: preverify_ok)
+
+ client = socket()
+ client.connect(('verisign.com', 443))
+ clientSSL = Connection(context, client)
+ clientSSL.set_connect_state()
+ clientSSL.do_handshake()
+ clientSSL.send(b"GET / HTTP/1.0\r\n\r\n")
+ self.assertTrue(clientSSL.recv(1024))
+
+
+ def test_set_default_verify_paths_signature(self):
+ """
+ :py:obj:`Context.set_default_verify_paths` takes no arguments and raises
+ :py:obj:`TypeError` if given any.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_default_verify_paths, None)
+ self.assertRaises(TypeError, context.set_default_verify_paths, 1)
+ self.assertRaises(TypeError, context.set_default_verify_paths, "")
+
+
+ def test_add_extra_chain_cert_invalid_cert(self):
+ """
+ :py:obj:`Context.add_extra_chain_cert` raises :py:obj:`TypeError` if called with
+ other than one argument or if called with an object which is not an
+ instance of :py:obj:`X509`.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.add_extra_chain_cert)
+ self.assertRaises(TypeError, context.add_extra_chain_cert, object())
+ self.assertRaises(TypeError, context.add_extra_chain_cert, object(), object())
+
+
+ def _handshake_test(self, serverContext, clientContext):
+ """
+ Verify that a client and server created with the given contexts can
+ successfully handshake and communicate.
+ """
+ serverSocket, clientSocket = socket_pair()
+
+ server = Connection(serverContext, serverSocket)
+ server.set_accept_state()
+
+ client = Connection(clientContext, clientSocket)
+ client.set_connect_state()
+
+ # Make them talk to each other.
+ # self._interactInMemory(client, server)
+ for i in range(3):
+ for s in [client, server]:
+ try:
+ s.do_handshake()
+ except WantReadError:
+ pass
+
+
+ def test_set_verify_callback_connection_argument(self):
+ """
+ The first argument passed to the verify callback is the
+ :py:class:`Connection` instance for which verification is taking place.
+ """
+ serverContext = Context(TLSv1_METHOD)
+ serverContext.use_privatekey(
+ load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
+ serverContext.use_certificate(
+ load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
+ serverConnection = Connection(serverContext, None)
+
+ class VerifyCallback(object):
+ def callback(self, connection, *args):
+ self.connection = connection
+ return 1
+
+ verify = VerifyCallback()
+ clientContext = Context(TLSv1_METHOD)
+ clientContext.set_verify(VERIFY_PEER, verify.callback)
+ clientConnection = Connection(clientContext, None)
+ clientConnection.set_connect_state()
+
+ self._handshakeInMemory(clientConnection, serverConnection)
+
+ self.assertIdentical(verify.connection, clientConnection)
+
+
+ def test_set_verify_callback_exception(self):
+ """
+ If the verify callback passed to :py:obj:`Context.set_verify` raises an
+ exception, verification fails and the exception is propagated to the
+ caller of :py:obj:`Connection.do_handshake`.
+ """
+ serverContext = Context(TLSv1_METHOD)
+ serverContext.use_privatekey(
+ load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
+ serverContext.use_certificate(
+ load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
+
+ clientContext = Context(TLSv1_METHOD)
+ def verify_callback(*args):
+ raise Exception("silly verify failure")
+ clientContext.set_verify(VERIFY_PEER, verify_callback)
+
+ exc = self.assertRaises(
+ Exception, self._handshake_test, serverContext, clientContext)
+ self.assertEqual("silly verify failure", str(exc))
+
+
+ def test_add_extra_chain_cert(self):
+ """
+ :py:obj:`Context.add_extra_chain_cert` accepts an :py:obj:`X509` instance to add to
+ the certificate chain.
+
+ See :py:obj:`_create_certificate_chain` for the details of the certificate
+ chain tested.
+
+ The chain is tested by starting a server with scert and connecting
+ to it with a client which trusts cacert and requires verification to
+ succeed.
+ """
+ chain = _create_certificate_chain()
+ [(cakey, cacert), (ikey, icert), (skey, scert)] = chain
+
+ # Dump the CA certificate to a file because that's the only way to load
+ # it as a trusted CA in the client context.
+ for cert, name in [(cacert, 'ca.pem'), (icert, 'i.pem'), (scert, 's.pem')]:
+ fObj = open(name, 'w')
+ fObj.write(dump_certificate(FILETYPE_PEM, cert).decode('ascii'))
+ fObj.close()
+
+ for key, name in [(cakey, 'ca.key'), (ikey, 'i.key'), (skey, 's.key')]:
+ fObj = open(name, 'w')
+ fObj.write(dump_privatekey(FILETYPE_PEM, key).decode('ascii'))
+ fObj.close()
+
+ # Create the server context
+ serverContext = Context(TLSv1_METHOD)
+ serverContext.use_privatekey(skey)
+ serverContext.use_certificate(scert)
+ # The client already has cacert, we only need to give them icert.
+ serverContext.add_extra_chain_cert(icert)
+
+ # Create the client
+ clientContext = Context(TLSv1_METHOD)
+ clientContext.set_verify(
+ VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb)
+ clientContext.load_verify_locations(b"ca.pem")
+
+ # Try it out.
+ self._handshake_test(serverContext, clientContext)
+
+
+ def _use_certificate_chain_file_test(self, certdir):
+ """
+ Verify that :py:obj:`Context.use_certificate_chain_file` reads a
+ certificate chain from a specified file.
+
+ The chain is tested by starting a server with scert and connecting to
+ it with a client which trusts cacert and requires verification to
+ succeed.
+ """
+ chain = _create_certificate_chain()
+ [(cakey, cacert), (ikey, icert), (skey, scert)] = chain
+
+ makedirs(certdir)
+
+ chainFile = join_bytes_or_unicode(certdir, "chain.pem")
+ caFile = join_bytes_or_unicode(certdir, "ca.pem")
+
+ # Write out the chain file.
+ with open(chainFile, 'wb') as fObj:
+ # Most specific to least general.
+ fObj.write(dump_certificate(FILETYPE_PEM, scert))
+ fObj.write(dump_certificate(FILETYPE_PEM, icert))
+ fObj.write(dump_certificate(FILETYPE_PEM, cacert))
+
+ with open(caFile, 'w') as fObj:
+ fObj.write(dump_certificate(FILETYPE_PEM, cacert).decode('ascii'))
+
+ serverContext = Context(TLSv1_METHOD)
+ serverContext.use_certificate_chain_file(chainFile)
+ serverContext.use_privatekey(skey)
+
+ clientContext = Context(TLSv1_METHOD)
+ clientContext.set_verify(
+ VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb)
+ clientContext.load_verify_locations(caFile)
+
+ self._handshake_test(serverContext, clientContext)
+
+
+ def test_use_certificate_chain_file_bytes(self):
+ """
+ ``Context.use_certificate_chain_file`` accepts the name of a file (as
+ an instance of ``bytes``) to specify additional certificates to use to
+ construct and verify a trust chain.
+ """
+ self._use_certificate_chain_file_test(
+ self.mktemp() + NON_ASCII.encode(getfilesystemencoding())
+ )
+
+
+ def test_use_certificate_chain_file_unicode(self):
+ """
+ ``Context.use_certificate_chain_file`` accepts the name of a file (as
+ an instance of ``unicode``) to specify additional certificates to use
+ to construct and verify a trust chain.
+ """
+ self._use_certificate_chain_file_test(
+ self.mktemp().decode(getfilesystemencoding()) + NON_ASCII
+ )
+
+
+ def test_use_certificate_chain_file_wrong_args(self):
+ """
+ :py:obj:`Context.use_certificate_chain_file` raises :py:obj:`TypeError`
+ if passed zero or more than one argument or when passed a non-byte
+ string single argument. It also raises :py:obj:`OpenSSL.SSL.Error` when
+ passed a bad chain file name (for example, the name of a file which does
+ not exist).
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.use_certificate_chain_file)
+ self.assertRaises(TypeError, context.use_certificate_chain_file, object())
+ self.assertRaises(TypeError, context.use_certificate_chain_file, b"foo", object())
+
+ self.assertRaises(Error, context.use_certificate_chain_file, self.mktemp())
+
+ # XXX load_client_ca
+ # XXX set_session_id
+
+ def test_get_verify_mode_wrong_args(self):
+ """
+ :py:obj:`Context.get_verify_mode` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.get_verify_mode, None)
+
+
+ def test_set_verify_mode(self):
+ """
+ :py:obj:`Context.get_verify_mode` returns the verify mode flags previously
+ passed to :py:obj:`Context.set_verify`.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertEquals(context.get_verify_mode(), 0)
+ context.set_verify(
+ VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None)
+ self.assertEquals(
+ context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE)
+
+
+ if not PY3:
+ def test_set_verify_mode_long(self):
+ """
+ On Python 2 :py:obj:`Context.set_verify_mode` accepts values of
+ type :py:obj:`long` as well as :py:obj:`int`.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertEquals(context.get_verify_mode(), 0)
+ context.set_verify(
+ long(VERIFY_PEER | VERIFY_CLIENT_ONCE), lambda *args: None)
+ self.assertEquals(
+ context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE)
+
+
+ def test_load_tmp_dh_wrong_args(self):
+ """
+ :py:obj:`Context.load_tmp_dh` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or with a non-:py:obj:`str` argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.load_tmp_dh)
+ self.assertRaises(TypeError, context.load_tmp_dh, "foo", None)
+ self.assertRaises(TypeError, context.load_tmp_dh, object())
+
+
+ def test_load_tmp_dh_missing_file(self):
+ """
+ :py:obj:`Context.load_tmp_dh` raises :py:obj:`OpenSSL.SSL.Error` if the specified file
+ does not exist.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(Error, context.load_tmp_dh, b"hello")
+
+
+ def _load_tmp_dh_test(self, dhfilename):
+ """
+ Verify that calling ``Context.load_tmp_dh`` with the given filename
+ does not raise an exception.
+ """
+ context = Context(TLSv1_METHOD)
+ with open(dhfilename, "w") as dhfile:
+ dhfile.write(dhparam)
+
+ context.load_tmp_dh(dhfilename)
+ # XXX What should I assert here? -exarkun
+
+
+ def test_load_tmp_dh_bytes(self):
+ """
+ :py:obj:`Context.load_tmp_dh` loads Diffie-Hellman parameters from the
+ specified file (given as ``bytes``).
+ """
+ self._load_tmp_dh_test(
+ self.mktemp() + NON_ASCII.encode(getfilesystemencoding()),
+ )
+
+
+ def test_load_tmp_dh_unicode(self):
+ """
+ :py:obj:`Context.load_tmp_dh` loads Diffie-Hellman parameters from the
+ specified file (given as ``unicode``).
+ """
+ self._load_tmp_dh_test(
+ self.mktemp().decode(getfilesystemencoding()) + NON_ASCII,
+ )
+
+
+ def test_set_tmp_ecdh(self):
+ """
+ :py:obj:`Context.set_tmp_ecdh` sets the elliptic curve for
+ Diffie-Hellman to the specified curve.
+ """
+ context = Context(TLSv1_METHOD)
+ for curve in get_elliptic_curves():
+ # The only easily "assertable" thing is that it does not raise an
+ # exception.
+ context.set_tmp_ecdh(curve)
+
+
+ def test_set_cipher_list_bytes(self):
+ """
+ :py:obj:`Context.set_cipher_list` accepts a :py:obj:`bytes` naming the
+ ciphers which connections created with the context object will be able
+ to choose from.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_cipher_list(b"hello world:EXP-RC4-MD5")
+ conn = Connection(context, None)
+ self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"])
+
+
+ def test_set_cipher_list_text(self):
+ """
+ :py:obj:`Context.set_cipher_list` accepts a :py:obj:`unicode` naming
+ the ciphers which connections created with the context object will be
+ able to choose from.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_cipher_list(u("hello world:EXP-RC4-MD5"))
+ conn = Connection(context, None)
+ self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"])
+
+
+ def test_set_cipher_list_wrong_args(self):
+ """
+ :py:obj:`Context.set_cipher_list` raises :py:obj:`TypeError` when
+ passed zero arguments or more than one argument or when passed a
+ non-string single argument and raises :py:obj:`OpenSSL.SSL.Error` when
+ passed an incorrect cipher list string.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_cipher_list)
+ self.assertRaises(TypeError, context.set_cipher_list, object())
+ self.assertRaises(TypeError, context.set_cipher_list, b"EXP-RC4-MD5", object())
+
+ self.assertRaises(Error, context.set_cipher_list, "imaginary-cipher")
+
+
+ def test_set_session_cache_mode_wrong_args(self):
+ """
+ :py:obj:`Context.set_session_cache_mode` raises :py:obj:`TypeError` if
+ called with other than one integer argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_session_cache_mode)
+ self.assertRaises(TypeError, context.set_session_cache_mode, object())
+
+
+ def test_get_session_cache_mode_wrong_args(self):
+ """
+ :py:obj:`Context.get_session_cache_mode` raises :py:obj:`TypeError` if
+ called with any arguments.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.get_session_cache_mode, 1)
+
+
+ def test_session_cache_mode(self):
+ """
+ :py:obj:`Context.set_session_cache_mode` specifies how sessions are
+ cached. The setting can be retrieved via
+ :py:obj:`Context.get_session_cache_mode`.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_session_cache_mode(SESS_CACHE_OFF)
+ off = context.set_session_cache_mode(SESS_CACHE_BOTH)
+ self.assertEqual(SESS_CACHE_OFF, off)
+ self.assertEqual(SESS_CACHE_BOTH, context.get_session_cache_mode())
+
+ if not PY3:
+ def test_session_cache_mode_long(self):
+ """
+ On Python 2 :py:obj:`Context.set_session_cache_mode` accepts values
+ of type :py:obj:`long` as well as :py:obj:`int`.
+ """
+ context = Context(TLSv1_METHOD)
+ context.set_session_cache_mode(long(SESS_CACHE_BOTH))
+ self.assertEqual(
+ SESS_CACHE_BOTH, context.get_session_cache_mode())
+
+
+ def test_get_cert_store(self):
+ """
+ :py:obj:`Context.get_cert_store` returns a :py:obj:`X509Store` instance.
+ """
+ context = Context(TLSv1_METHOD)
+ store = context.get_cert_store()
+ self.assertIsInstance(store, X509Store)
+
+
+
+class ServerNameCallbackTests(TestCase, _LoopbackMixin):
+ """
+ Tests for :py:obj:`Context.set_tlsext_servername_callback` and its interaction with
+ :py:obj:`Connection`.
+ """
+ def test_wrong_args(self):
+ """
+ :py:obj:`Context.set_tlsext_servername_callback` raises :py:obj:`TypeError` if called
+ with other than one argument.
+ """
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, context.set_tlsext_servername_callback)
+ self.assertRaises(
+ TypeError, context.set_tlsext_servername_callback, 1, 2)
+
+
+ def test_old_callback_forgotten(self):
+ """
+ If :py:obj:`Context.set_tlsext_servername_callback` is used to specify a new
+ callback, the one it replaces is dereferenced.
+ """
+ def callback(connection):
+ pass
+
+ def replacement(connection):
+ pass
+
+ context = Context(TLSv1_METHOD)
+ context.set_tlsext_servername_callback(callback)
+
+ tracker = ref(callback)
+ del callback
+
+ context.set_tlsext_servername_callback(replacement)
+
+ # One run of the garbage collector happens to work on CPython. PyPy
+ # doesn't collect the underlying object until a second run for whatever
+ # reason. That's fine, it still demonstrates our code has properly
+ # dropped the reference.
+ collect()
+ collect()
+
+ callback = tracker()
+ if callback is not None:
+ referrers = get_referrers(callback)
+ if len(referrers) > 1:
+ self.fail("Some references remain: %r" % (referrers,))
+
+
+ def test_no_servername(self):
+ """
+ When a client specifies no server name, the callback passed to
+ :py:obj:`Context.set_tlsext_servername_callback` is invoked and the result of
+ :py:obj:`Connection.get_servername` is :py:obj:`None`.
+ """
+ args = []
+ def servername(conn):
+ args.append((conn, conn.get_servername()))
+ context = Context(TLSv1_METHOD)
+ context.set_tlsext_servername_callback(servername)
+
+ # Lose our reference to it. The Context is responsible for keeping it
+ # alive now.
+ del servername
+ collect()
+
+ # Necessary to actually accept the connection
+ context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ context.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(context, None)
+ server.set_accept_state()
+
+ client = Connection(Context(TLSv1_METHOD), None)
+ client.set_connect_state()
+
+ self._interactInMemory(server, client)
+
+ self.assertEqual([(server, None)], args)
+
+
+ def test_servername(self):
+ """
+ When a client specifies a server name in its hello message, the callback
+ passed to :py:obj:`Contexts.set_tlsext_servername_callback` is invoked and the
+ result of :py:obj:`Connection.get_servername` is that server name.
+ """
+ args = []
+ def servername(conn):
+ args.append((conn, conn.get_servername()))
+ context = Context(TLSv1_METHOD)
+ context.set_tlsext_servername_callback(servername)
+
+ # Necessary to actually accept the connection
+ context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ context.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(context, None)
+ server.set_accept_state()
+
+ client = Connection(Context(TLSv1_METHOD), None)
+ client.set_connect_state()
+ client.set_tlsext_host_name(b("foo1.example.com"))
+
+ self._interactInMemory(server, client)
+
+ self.assertEqual([(server, b("foo1.example.com"))], args)
+
+
+class NextProtoNegotiationTests(TestCase, _LoopbackMixin):
+ """
+ Test for Next Protocol Negotiation in PyOpenSSL.
+ """
+ if _lib.Cryptography_HAS_NEXTPROTONEG:
+ def test_npn_success(self):
+ """
+ Tests that clients and servers that agree on the negotiated next
+ protocol can correct establish a connection, and that the agreed
+ protocol is reported by the connections.
+ """
+ advertise_args = []
+ select_args = []
+ def advertise(conn):
+ advertise_args.append((conn,))
+ return [b'http/1.1', b'spdy/2']
+ def select(conn, options):
+ select_args.append((conn, options))
+ return b'spdy/2'
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_npn_advertise_callback(advertise)
+
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_npn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ self._interactInMemory(server, client)
+
+ self.assertEqual([(server,)], advertise_args)
+ self.assertEqual([(client, [b'http/1.1', b'spdy/2'])], select_args)
+
+ self.assertEqual(server.get_next_proto_negotiated(), b'spdy/2')
+ self.assertEqual(client.get_next_proto_negotiated(), b'spdy/2')
+
+
+ def test_npn_client_fail(self):
+ """
+ Tests that when clients and servers cannot agree on what protocol
+ to use next that the TLS connection does not get established.
+ """
+ advertise_args = []
+ select_args = []
+ def advertise(conn):
+ advertise_args.append((conn,))
+ return [b'http/1.1', b'spdy/2']
+ def select(conn, options):
+ select_args.append((conn, options))
+ return b''
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_npn_advertise_callback(advertise)
+
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_npn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ # If the client doesn't return anything, the connection will fail.
+ self.assertRaises(Error, self._interactInMemory, server, client)
+
+ self.assertEqual([(server,)], advertise_args)
+ self.assertEqual([(client, [b'http/1.1', b'spdy/2'])], select_args)
+
+
+ def test_npn_select_error(self):
+ """
+ Test that we can handle exceptions in the select callback. If
+ select fails it should be fatal to the connection.
+ """
+ advertise_args = []
+ def advertise(conn):
+ advertise_args.append((conn,))
+ return [b'http/1.1', b'spdy/2']
+ def select(conn, options):
+ raise TypeError
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_npn_advertise_callback(advertise)
+
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_npn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ # If the callback throws an exception it should be raised here.
+ self.assertRaises(
+ TypeError, self._interactInMemory, server, client
+ )
+ self.assertEqual([(server,)], advertise_args)
+
+
+ def test_npn_advertise_error(self):
+ """
+ Test that we can handle exceptions in the advertise callback. If
+ advertise fails no NPN is advertised to the client.
+ """
+ select_args = []
+ def advertise(conn):
+ raise TypeError
+ def select(conn, options):
+ select_args.append((conn, options))
+ return b''
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_npn_advertise_callback(advertise)
+
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_npn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ # If the client doesn't return anything, the connection will fail.
+ self.assertRaises(
+ TypeError, self._interactInMemory, server, client
+ )
+ self.assertEqual([], select_args)
+
+ else:
+ # No NPN.
+ def test_npn_not_implemented(self):
+ # Test the context methods first.
+ context = Context(TLSv1_METHOD)
+ fail_methods = [
+ context.set_npn_advertise_callback,
+ context.set_npn_select_callback,
+ ]
+ for method in fail_methods:
+ self.assertRaises(
+ NotImplementedError, method, None
+ )
+
+ # Now test a connection.
+ conn = Connection(context)
+ fail_methods = [
+ conn.get_next_proto_negotiated,
+ ]
+ for method in fail_methods:
+ self.assertRaises(NotImplementedError, method)
+
+
+
+class ApplicationLayerProtoNegotiationTests(TestCase, _LoopbackMixin):
+ """
+ Tests for ALPN in PyOpenSSL.
+ """
+ # Skip tests on versions that don't support ALPN.
+ if _lib.Cryptography_HAS_ALPN:
+
+ def test_alpn_success(self):
+ """
+ Clients and servers that agree on the negotiated ALPN protocol can
+ correct establish a connection, and the agreed protocol is reported
+ by the connections.
+ """
+ select_args = []
+ def select(conn, options):
+ select_args.append((conn, options))
+ return b'spdy/2'
+
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_alpn_protos([b'http/1.1', b'spdy/2'])
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_alpn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ self._interactInMemory(server, client)
+
+ self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args)
+
+ self.assertEqual(server.get_alpn_proto_negotiated(), b'spdy/2')
+ self.assertEqual(client.get_alpn_proto_negotiated(), b'spdy/2')
+
+
+ def test_alpn_set_on_connection(self):
+ """
+ The same as test_alpn_success, but setting the ALPN protocols on
+ the connection rather than the context.
+ """
+ select_args = []
+ def select(conn, options):
+ select_args.append((conn, options))
+ return b'spdy/2'
+
+ # Setup the client context but don't set any ALPN protocols.
+ client_context = Context(TLSv1_METHOD)
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_alpn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ # Set the ALPN protocols on the client connection.
+ client = Connection(client_context, None)
+ client.set_alpn_protos([b'http/1.1', b'spdy/2'])
+ client.set_connect_state()
+
+ self._interactInMemory(server, client)
+
+ self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args)
+
+ self.assertEqual(server.get_alpn_proto_negotiated(), b'spdy/2')
+ self.assertEqual(client.get_alpn_proto_negotiated(), b'spdy/2')
+
+
+ def test_alpn_server_fail(self):
+ """
+ When clients and servers cannot agree on what protocol to use next
+ the TLS connection does not get established.
+ """
+ select_args = []
+ def select(conn, options):
+ select_args.append((conn, options))
+ return b''
+
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_alpn_protos([b'http/1.1', b'spdy/2'])
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_alpn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ # If the client doesn't return anything, the connection will fail.
+ self.assertRaises(Error, self._interactInMemory, server, client)
+
+ self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args)
+
+
+ def test_alpn_no_server(self):
+ """
+ When clients and servers cannot agree on what protocol to use next
+ because the server doesn't offer ALPN, no protocol is negotiated.
+ """
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_alpn_protos([b'http/1.1', b'spdy/2'])
+
+ server_context = Context(TLSv1_METHOD)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ # Do the dance.
+ self._interactInMemory(server, client)
+
+ self.assertEqual(client.get_alpn_proto_negotiated(), b'')
+
+
+ def test_alpn_callback_exception(self):
+ """
+ We can handle exceptions in the ALPN select callback.
+ """
+ select_args = []
+ def select(conn, options):
+ select_args.append((conn, options))
+ raise TypeError()
+
+ client_context = Context(TLSv1_METHOD)
+ client_context.set_alpn_protos([b'http/1.1', b'spdy/2'])
+
+ server_context = Context(TLSv1_METHOD)
+ server_context.set_alpn_select_callback(select)
+
+ # Necessary to actually accept the connection
+ server_context.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_context.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+
+ # Do a little connection to trigger the logic
+ server = Connection(server_context, None)
+ server.set_accept_state()
+
+ client = Connection(client_context, None)
+ client.set_connect_state()
+
+ self.assertRaises(
+ TypeError, self._interactInMemory, server, client
+ )
+ self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args)
+
+ else:
+ # No ALPN.
+ def test_alpn_not_implemented(self):
+ """
+ If ALPN is not in OpenSSL, we should raise NotImplementedError.
+ """
+ # Test the context methods first.
+ context = Context(TLSv1_METHOD)
+ self.assertRaises(
+ NotImplementedError, context.set_alpn_protos, None
+ )
+ self.assertRaises(
+ NotImplementedError, context.set_alpn_select_callback, None
+ )
+
+ # Now test a connection.
+ conn = Connection(context)
+ self.assertRaises(
+ NotImplementedError, context.set_alpn_protos, None
+ )
+
+
+
+class SessionTests(TestCase):
+ """
+ Unit tests for :py:obj:`OpenSSL.SSL.Session`.
+ """
+ def test_construction(self):
+ """
+ :py:class:`Session` can be constructed with no arguments, creating a new
+ instance of that type.
+ """
+ new_session = Session()
+ self.assertTrue(isinstance(new_session, Session))
+
+
+ def test_construction_wrong_args(self):
+ """
+ If any arguments are passed to :py:class:`Session`, :py:obj:`TypeError`
+ is raised.
+ """
+ self.assertRaises(TypeError, Session, 123)
+ self.assertRaises(TypeError, Session, "hello")
+ self.assertRaises(TypeError, Session, object())
+
+
+
+class ConnectionTests(TestCase, _LoopbackMixin):
+ """
+ Unit tests for :py:obj:`OpenSSL.SSL.Connection`.
+ """
+ # XXX get_peer_certificate -> None
+ # XXX sock_shutdown
+ # XXX master_key -> TypeError
+ # XXX server_random -> TypeError
+ # XXX state_string
+ # XXX connect -> TypeError
+ # XXX connect_ex -> TypeError
+ # XXX set_connect_state -> TypeError
+ # XXX set_accept_state -> TypeError
+ # XXX renegotiate_pending
+ # XXX do_handshake -> TypeError
+ # XXX bio_read -> TypeError
+ # XXX recv -> TypeError
+ # XXX send -> TypeError
+ # XXX bio_write -> TypeError
+
+ def test_type(self):
+ """
+ :py:obj:`Connection` and :py:obj:`ConnectionType` refer to the same type object and
+ can be used to create instances of that type.
+ """
+ self.assertIdentical(Connection, ConnectionType)
+ ctx = Context(TLSv1_METHOD)
+ self.assertConsistentType(Connection, 'Connection', ctx, None)
+
+
+ def test_get_context(self):
+ """
+ :py:obj:`Connection.get_context` returns the :py:obj:`Context` instance used to
+ construct the :py:obj:`Connection` instance.
+ """
+ context = Context(TLSv1_METHOD)
+ connection = Connection(context, None)
+ self.assertIdentical(connection.get_context(), context)
+
+
+ def test_get_context_wrong_args(self):
+ """
+ :py:obj:`Connection.get_context` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.get_context, None)
+
+
+ def test_set_context_wrong_args(self):
+ """
+ :py:obj:`Connection.set_context` raises :py:obj:`TypeError` if called with a
+ non-:py:obj:`Context` instance argument or with any number of arguments other
+ than 1.
+ """
+ ctx = Context(TLSv1_METHOD)
+ connection = Connection(ctx, None)
+ self.assertRaises(TypeError, connection.set_context)
+ self.assertRaises(TypeError, connection.set_context, object())
+ self.assertRaises(TypeError, connection.set_context, "hello")
+ self.assertRaises(TypeError, connection.set_context, 1)
+ self.assertRaises(TypeError, connection.set_context, 1, 2)
+ self.assertRaises(
+ TypeError, connection.set_context, Context(TLSv1_METHOD), 2)
+ self.assertIdentical(ctx, connection.get_context())
+
+
+ def test_set_context(self):
+ """
+ :py:obj:`Connection.set_context` specifies a new :py:obj:`Context` instance to be used
+ for the connection.
+ """
+ original = Context(SSLv23_METHOD)
+ replacement = Context(TLSv1_METHOD)
+ connection = Connection(original, None)
+ connection.set_context(replacement)
+ self.assertIdentical(replacement, connection.get_context())
+ # Lose our references to the contexts, just in case the Connection isn't
+ # properly managing its own contributions to their reference counts.
+ del original, replacement
+ collect()
+
+
+ def test_set_tlsext_host_name_wrong_args(self):
+ """
+ If :py:obj:`Connection.set_tlsext_host_name` is called with a non-byte string
+ argument or a byte string with an embedded NUL or other than one
+ argument, :py:obj:`TypeError` is raised.
+ """
+ conn = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, conn.set_tlsext_host_name)
+ self.assertRaises(TypeError, conn.set_tlsext_host_name, object())
+ self.assertRaises(TypeError, conn.set_tlsext_host_name, 123, 456)
+ self.assertRaises(
+ TypeError, conn.set_tlsext_host_name, b("with\0null"))
+
+ if PY3:
+ # On Python 3.x, don't accidentally implicitly convert from text.
+ self.assertRaises(
+ TypeError,
+ conn.set_tlsext_host_name, b("example.com").decode("ascii"))
+
+
+ def test_get_servername_wrong_args(self):
+ """
+ :py:obj:`Connection.get_servername` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.get_servername, object())
+ self.assertRaises(TypeError, connection.get_servername, 1)
+ self.assertRaises(TypeError, connection.get_servername, "hello")
+
+
+ def test_pending(self):
+ """
+ :py:obj:`Connection.pending` returns the number of bytes available for
+ immediate read.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertEquals(connection.pending(), 0)
+
+
+ def test_pending_wrong_args(self):
+ """
+ :py:obj:`Connection.pending` raises :py:obj:`TypeError` if called with any arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.pending, None)
+
+
+ def test_connect_wrong_args(self):
+ """
+ :py:obj:`Connection.connect` raises :py:obj:`TypeError` if called with a non-address
+ argument or with the wrong number of arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), socket())
+ self.assertRaises(TypeError, connection.connect, None)
+ self.assertRaises(TypeError, connection.connect)
+ self.assertRaises(TypeError, connection.connect, ("127.0.0.1", 1), None)
+
+
+ def test_connect_refused(self):
+ """
+ :py:obj:`Connection.connect` raises :py:obj:`socket.error` if the underlying socket
+ connect method raises it.
+ """
+ client = socket()
+ context = Context(TLSv1_METHOD)
+ clientSSL = Connection(context, client)
+ exc = self.assertRaises(error, clientSSL.connect, ("127.0.0.1", 1))
+ self.assertEquals(exc.args[0], ECONNREFUSED)
+
+
+ def test_connect(self):
+ """
+ :py:obj:`Connection.connect` establishes a connection to the specified address.
+ """
+ port = socket()
+ port.bind(('', 0))
+ port.listen(3)
+
+ clientSSL = Connection(Context(TLSv1_METHOD), socket())
+ clientSSL.connect(('127.0.0.1', port.getsockname()[1]))
+ # XXX An assertion? Or something?
+
+
+ if platform == "darwin":
+ "connect_ex sometimes causes a kernel panic on OS X 10.6.4"
+ else:
+ def test_connect_ex(self):
+ """
+ If there is a connection error, :py:obj:`Connection.connect_ex` returns the
+ errno instead of raising an exception.
+ """
+ port = socket()
+ port.bind(('', 0))
+ port.listen(3)
+
+ clientSSL = Connection(Context(TLSv1_METHOD), socket())
+ clientSSL.setblocking(False)
+ result = clientSSL.connect_ex(port.getsockname())
+ expected = (EINPROGRESS, EWOULDBLOCK)
+ self.assertTrue(
+ result in expected, "%r not in %r" % (result, expected))
+
+
+ def test_accept_wrong_args(self):
+ """
+ :py:obj:`Connection.accept` raises :py:obj:`TypeError` if called with any arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), socket())
+ self.assertRaises(TypeError, connection.accept, None)
+
+
+ def test_accept(self):
+ """
+ :py:obj:`Connection.accept` accepts a pending connection attempt and returns a
+ tuple of a new :py:obj:`Connection` (the accepted client) and the address the
+ connection originated from.
+ """
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+ port = socket()
+ portSSL = Connection(ctx, port)
+ portSSL.bind(('', 0))
+ portSSL.listen(3)
+
+ clientSSL = Connection(Context(TLSv1_METHOD), socket())
+
+ # Calling portSSL.getsockname() here to get the server IP address sounds
+ # great, but frequently fails on Windows.
+ clientSSL.connect(('127.0.0.1', portSSL.getsockname()[1]))
+
+ serverSSL, address = portSSL.accept()
+
+ self.assertTrue(isinstance(serverSSL, Connection))
+ self.assertIdentical(serverSSL.get_context(), ctx)
+ self.assertEquals(address, clientSSL.getsockname())
+
+
+ def test_shutdown_wrong_args(self):
+ """
+ :py:obj:`Connection.shutdown` raises :py:obj:`TypeError` if called with the wrong
+ number of arguments or with arguments other than integers.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.shutdown, None)
+ self.assertRaises(TypeError, connection.get_shutdown, None)
+ self.assertRaises(TypeError, connection.set_shutdown)
+ self.assertRaises(TypeError, connection.set_shutdown, None)
+ self.assertRaises(TypeError, connection.set_shutdown, 0, 1)
+
+
+ def test_shutdown(self):
+ """
+ :py:obj:`Connection.shutdown` performs an SSL-level connection shutdown.
+ """
+ server, client = self._loopback()
+ self.assertFalse(server.shutdown())
+ self.assertEquals(server.get_shutdown(), SENT_SHUTDOWN)
+ self.assertRaises(ZeroReturnError, client.recv, 1024)
+ self.assertEquals(client.get_shutdown(), RECEIVED_SHUTDOWN)
+ client.shutdown()
+ self.assertEquals(client.get_shutdown(), SENT_SHUTDOWN|RECEIVED_SHUTDOWN)
+ self.assertRaises(ZeroReturnError, server.recv, 1024)
+ self.assertEquals(server.get_shutdown(), SENT_SHUTDOWN|RECEIVED_SHUTDOWN)
+
+
+ def test_shutdown_closed(self):
+ """
+ If the underlying socket is closed, :py:obj:`Connection.shutdown` propagates the
+ write error from the low level write call.
+ """
+ server, client = self._loopback()
+ server.sock_shutdown(2)
+ exc = self.assertRaises(SysCallError, server.shutdown)
+ if platform == "win32":
+ self.assertEqual(exc.args[0], ESHUTDOWN)
+ else:
+ self.assertEqual(exc.args[0], EPIPE)
+
+
+ def test_shutdown_truncated(self):
+ """
+ If the underlying connection is truncated, :obj:`Connection.shutdown`
+ raises an :obj:`Error`.
+ """
+ server_ctx = Context(TLSv1_METHOD)
+ client_ctx = Context(TLSv1_METHOD)
+ server_ctx.use_privatekey(
+ load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_ctx.use_certificate(
+ load_certificate(FILETYPE_PEM, server_cert_pem))
+ server = Connection(server_ctx, None)
+ client = Connection(client_ctx, None)
+ self._handshakeInMemory(client, server)
+ self.assertEqual(server.shutdown(), False)
+ self.assertRaises(WantReadError, server.shutdown)
+ server.bio_shutdown()
+ self.assertRaises(Error, server.shutdown)
+
+
+ def test_set_shutdown(self):
+ """
+ :py:obj:`Connection.set_shutdown` sets the state of the SSL connection shutdown
+ process.
+ """
+ connection = Connection(Context(TLSv1_METHOD), socket())
+ connection.set_shutdown(RECEIVED_SHUTDOWN)
+ self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN)
+
+
+ if not PY3:
+ def test_set_shutdown_long(self):
+ """
+ On Python 2 :py:obj:`Connection.set_shutdown` accepts an argument
+ of type :py:obj:`long` as well as :py:obj:`int`.
+ """
+ connection = Connection(Context(TLSv1_METHOD), socket())
+ connection.set_shutdown(long(RECEIVED_SHUTDOWN))
+ self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN)
+
+
+ def test_app_data_wrong_args(self):
+ """
+ :py:obj:`Connection.set_app_data` raises :py:obj:`TypeError` if called with other than
+ one argument. :py:obj:`Connection.get_app_data` raises :py:obj:`TypeError` if called
+ with any arguments.
+ """
+ conn = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, conn.get_app_data, None)
+ self.assertRaises(TypeError, conn.set_app_data)
+ self.assertRaises(TypeError, conn.set_app_data, None, None)
+
+
+ def test_app_data(self):
+ """
+ Any object can be set as app data by passing it to
+ :py:obj:`Connection.set_app_data` and later retrieved with
+ :py:obj:`Connection.get_app_data`.
+ """
+ conn = Connection(Context(TLSv1_METHOD), None)
+ app_data = object()
+ conn.set_app_data(app_data)
+ self.assertIdentical(conn.get_app_data(), app_data)
+
+
+ def test_makefile(self):
+ """
+ :py:obj:`Connection.makefile` is not implemented and calling that method raises
+ :py:obj:`NotImplementedError`.
+ """
+ conn = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(NotImplementedError, conn.makefile)
+
+
+ def test_get_peer_cert_chain_wrong_args(self):
+ """
+ :py:obj:`Connection.get_peer_cert_chain` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ conn = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, conn.get_peer_cert_chain, 1)
+ self.assertRaises(TypeError, conn.get_peer_cert_chain, "foo")
+ self.assertRaises(TypeError, conn.get_peer_cert_chain, object())
+ self.assertRaises(TypeError, conn.get_peer_cert_chain, [])
+
+
+ def test_get_peer_cert_chain(self):
+ """
+ :py:obj:`Connection.get_peer_cert_chain` returns a list of certificates which
+ the connected server returned for the certification verification.
+ """
+ chain = _create_certificate_chain()
+ [(cakey, cacert), (ikey, icert), (skey, scert)] = chain
+
+ serverContext = Context(TLSv1_METHOD)
+ serverContext.use_privatekey(skey)
+ serverContext.use_certificate(scert)
+ serverContext.add_extra_chain_cert(icert)
+ serverContext.add_extra_chain_cert(cacert)
+ server = Connection(serverContext, None)
+ server.set_accept_state()
+
+ # Create the client
+ clientContext = Context(TLSv1_METHOD)
+ clientContext.set_verify(VERIFY_NONE, verify_cb)
+ client = Connection(clientContext, None)
+ client.set_connect_state()
+
+ self._interactInMemory(client, server)
+
+ chain = client.get_peer_cert_chain()
+ self.assertEqual(len(chain), 3)
+ self.assertEqual(
+ "Server Certificate", chain[0].get_subject().CN)
+ self.assertEqual(
+ "Intermediate Certificate", chain[1].get_subject().CN)
+ self.assertEqual(
+ "Authority Certificate", chain[2].get_subject().CN)
+
+
+ def test_get_peer_cert_chain_none(self):
+ """
+ :py:obj:`Connection.get_peer_cert_chain` returns :py:obj:`None` if the peer sends no
+ certificate chain.
+ """
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+ server = Connection(ctx, None)
+ server.set_accept_state()
+ client = Connection(Context(TLSv1_METHOD), None)
+ client.set_connect_state()
+ self._interactInMemory(client, server)
+ self.assertIdentical(None, server.get_peer_cert_chain())
+
+
+ def test_get_session_wrong_args(self):
+ """
+ :py:obj:`Connection.get_session` raises :py:obj:`TypeError` if called
+ with any arguments.
+ """
+ ctx = Context(TLSv1_METHOD)
+ server = Connection(ctx, None)
+ self.assertRaises(TypeError, server.get_session, 123)
+ self.assertRaises(TypeError, server.get_session, "hello")
+ self.assertRaises(TypeError, server.get_session, object())
+
+
+ def test_get_session_unconnected(self):
+ """
+ :py:obj:`Connection.get_session` returns :py:obj:`None` when used with
+ an object which has not been connected.
+ """
+ ctx = Context(TLSv1_METHOD)
+ server = Connection(ctx, None)
+ session = server.get_session()
+ self.assertIdentical(None, session)
+
+
+ def test_server_get_session(self):
+ """
+ On the server side of a connection, :py:obj:`Connection.get_session`
+ returns a :py:class:`Session` instance representing the SSL session for
+ that connection.
+ """
+ server, client = self._loopback()
+ session = server.get_session()
+ self.assertIsInstance(session, Session)
+
+
+ def test_client_get_session(self):
+ """
+ On the client side of a connection, :py:obj:`Connection.get_session`
+ returns a :py:class:`Session` instance representing the SSL session for
+ that connection.
+ """
+ server, client = self._loopback()
+ session = client.get_session()
+ self.assertIsInstance(session, Session)
+
+
+ def test_set_session_wrong_args(self):
+ """
+ If called with an object that is not an instance of :py:class:`Session`,
+ or with other than one argument, :py:obj:`Connection.set_session` raises
+ :py:obj:`TypeError`.
+ """
+ ctx = Context(TLSv1_METHOD)
+ connection = Connection(ctx, None)
+ self.assertRaises(TypeError, connection.set_session)
+ self.assertRaises(TypeError, connection.set_session, 123)
+ self.assertRaises(TypeError, connection.set_session, "hello")
+ self.assertRaises(TypeError, connection.set_session, object())
+ self.assertRaises(
+ TypeError, connection.set_session, Session(), Session())
+
+
+ def test_client_set_session(self):
+ """
+ :py:obj:`Connection.set_session`, when used prior to a connection being
+ established, accepts a :py:class:`Session` instance and causes an
+ attempt to re-use the session it represents when the SSL handshake is
+ performed.
+ """
+ key = load_privatekey(FILETYPE_PEM, server_key_pem)
+ cert = load_certificate(FILETYPE_PEM, server_cert_pem)
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_privatekey(key)
+ ctx.use_certificate(cert)
+ ctx.set_session_id("unity-test")
+
+ def makeServer(socket):
+ server = Connection(ctx, socket)
+ server.set_accept_state()
+ return server
+
+ originalServer, originalClient = self._loopback(
+ serverFactory=makeServer)
+ originalSession = originalClient.get_session()
+
+ def makeClient(socket):
+ client = self._loopbackClientFactory(socket)
+ client.set_session(originalSession)
+ return client
+ resumedServer, resumedClient = self._loopback(
+ serverFactory=makeServer,
+ clientFactory=makeClient)
+
+ # This is a proxy: in general, we have no access to any unique
+ # identifier for the session (new enough versions of OpenSSL expose a
+ # hash which could be usable, but "new enough" is very, very new).
+ # Instead, exploit the fact that the master key is re-used if the
+ # session is re-used. As long as the master key for the two connections
+ # is the same, the session was re-used!
+ self.assertEqual(
+ originalServer.master_key(), resumedServer.master_key())
+
+
+ def test_set_session_wrong_method(self):
+ """
+ If :py:obj:`Connection.set_session` is passed a :py:class:`Session`
+ instance associated with a context using a different SSL method than the
+ :py:obj:`Connection` is using, a :py:class:`OpenSSL.SSL.Error` is
+ raised.
+ """
+ key = load_privatekey(FILETYPE_PEM, server_key_pem)
+ cert = load_certificate(FILETYPE_PEM, server_cert_pem)
+ ctx = Context(TLSv1_METHOD)
+ ctx.use_privatekey(key)
+ ctx.use_certificate(cert)
+ ctx.set_session_id("unity-test")
+
+ def makeServer(socket):
+ server = Connection(ctx, socket)
+ server.set_accept_state()
+ return server
+
+ originalServer, originalClient = self._loopback(
+ serverFactory=makeServer)
+ originalSession = originalClient.get_session()
+
+ def makeClient(socket):
+ # Intentionally use a different, incompatible method here.
+ client = Connection(Context(SSLv3_METHOD), socket)
+ client.set_connect_state()
+ client.set_session(originalSession)
+ return client
+
+ self.assertRaises(
+ Error,
+ self._loopback, clientFactory=makeClient, serverFactory=makeServer)
+
+
+ def test_wantWriteError(self):
+ """
+ :py:obj:`Connection` methods which generate output raise
+ :py:obj:`OpenSSL.SSL.WantWriteError` if writing to the connection's BIO
+ fail indicating a should-write state.
+ """
+ client_socket, server_socket = socket_pair()
+ # Fill up the client's send buffer so Connection won't be able to write
+ # anything. Only write a single byte at a time so we can be sure we
+ # completely fill the buffer. Even though the socket API is allowed to
+ # signal a short write via its return value it seems this doesn't
+ # always happen on all platforms (FreeBSD and OS X particular) for the
+ # very last bit of available buffer space.
+ msg = b"x"
+ for i in range(1024 * 1024 * 4):
+ try:
+ client_socket.send(msg)
+ except error as e:
+ if e.errno == EWOULDBLOCK:
+ break
+ raise
+ else:
+ self.fail(
+ "Failed to fill socket buffer, cannot test BIO want write")
+
+ ctx = Context(TLSv1_METHOD)
+ conn = Connection(ctx, client_socket)
+ # Client's speak first, so make it an SSL client
+ conn.set_connect_state()
+ self.assertRaises(WantWriteError, conn.do_handshake)
+
+ # XXX want_read
+
+ def test_get_finished_before_connect(self):
+ """
+ :py:obj:`Connection.get_finished` returns :py:obj:`None` before TLS
+ handshake is completed.
+ """
+ ctx = Context(TLSv1_METHOD)
+ connection = Connection(ctx, None)
+ self.assertEqual(connection.get_finished(), None)
+
+
+ def test_get_peer_finished_before_connect(self):
+ """
+ :py:obj:`Connection.get_peer_finished` returns :py:obj:`None` before
+ TLS handshake is completed.
+ """
+ ctx = Context(TLSv1_METHOD)
+ connection = Connection(ctx, None)
+ self.assertEqual(connection.get_peer_finished(), None)
+
+
+ def test_get_finished(self):
+ """
+ :py:obj:`Connection.get_finished` method returns the TLS Finished
+ message send from client, or server. Finished messages are send during
+ TLS handshake.
+ """
+
+ server, client = self._loopback()
+
+ self.assertNotEqual(server.get_finished(), None)
+ self.assertTrue(len(server.get_finished()) > 0)
+
+
+ def test_get_peer_finished(self):
+ """
+ :py:obj:`Connection.get_peer_finished` method returns the TLS Finished
+ message received from client, or server. Finished messages are send
+ during TLS handshake.
+ """
+ server, client = self._loopback()
+
+ self.assertNotEqual(server.get_peer_finished(), None)
+ self.assertTrue(len(server.get_peer_finished()) > 0)
+
+
+ def test_tls_finished_message_symmetry(self):
+ """
+ The TLS Finished message send by server must be the TLS Finished message
+ received by client.
+
+ The TLS Finished message send by client must be the TLS Finished message
+ received by server.
+ """
+ server, client = self._loopback()
+
+ self.assertEqual(server.get_finished(), client.get_peer_finished())
+ self.assertEqual(client.get_finished(), server.get_peer_finished())
+
+
+ def test_get_cipher_name_before_connect(self):
+ """
+ :py:obj:`Connection.get_cipher_name` returns :py:obj:`None` if no
+ connection has been established.
+ """
+ ctx = Context(TLSv1_METHOD)
+ conn = Connection(ctx, None)
+ self.assertIdentical(conn.get_cipher_name(), None)
+
+
+ def test_get_cipher_name(self):
+ """
+ :py:obj:`Connection.get_cipher_name` returns a :py:class:`unicode`
+ string giving the name of the currently used cipher.
+ """
+ server, client = self._loopback()
+ server_cipher_name, client_cipher_name = \
+ server.get_cipher_name(), client.get_cipher_name()
+
+ self.assertIsInstance(server_cipher_name, text_type)
+ self.assertIsInstance(client_cipher_name, text_type)
+
+ self.assertEqual(server_cipher_name, client_cipher_name)
+
+
+ def test_get_cipher_version_before_connect(self):
+ """
+ :py:obj:`Connection.get_cipher_version` returns :py:obj:`None` if no
+ connection has been established.
+ """
+ ctx = Context(TLSv1_METHOD)
+ conn = Connection(ctx, None)
+ self.assertIdentical(conn.get_cipher_version(), None)
+
+
+ def test_get_cipher_version(self):
+ """
+ :py:obj:`Connection.get_cipher_version` returns a :py:class:`unicode`
+ string giving the protocol name of the currently used cipher.
+ """
+ server, client = self._loopback()
+ server_cipher_version, client_cipher_version = \
+ server.get_cipher_version(), client.get_cipher_version()
+
+ self.assertIsInstance(server_cipher_version, text_type)
+ self.assertIsInstance(client_cipher_version, text_type)
+
+ self.assertEqual(server_cipher_version, client_cipher_version)
+
+
+ def test_get_cipher_bits_before_connect(self):
+ """
+ :py:obj:`Connection.get_cipher_bits` returns :py:obj:`None` if no
+ connection has been established.
+ """
+ ctx = Context(TLSv1_METHOD)
+ conn = Connection(ctx, None)
+ self.assertIdentical(conn.get_cipher_bits(), None)
+
+
+ def test_get_cipher_bits(self):
+ """
+ :py:obj:`Connection.get_cipher_bits` returns the number of secret bits
+ of the currently used cipher.
+ """
+ server, client = self._loopback()
+ server_cipher_bits, client_cipher_bits = \
+ server.get_cipher_bits(), client.get_cipher_bits()
+
+ self.assertIsInstance(server_cipher_bits, int)
+ self.assertIsInstance(client_cipher_bits, int)
+
+ self.assertEqual(server_cipher_bits, client_cipher_bits)
+
+
+
+class ConnectionGetCipherListTests(TestCase):
+ """
+ Tests for :py:obj:`Connection.get_cipher_list`.
+ """
+ def test_wrong_args(self):
+ """
+ :py:obj:`Connection.get_cipher_list` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.get_cipher_list, None)
+
+
+ def test_result(self):
+ """
+ :py:obj:`Connection.get_cipher_list` returns a :py:obj:`list` of
+ :py:obj:`bytes` giving the names of the ciphers which might be used.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ ciphers = connection.get_cipher_list()
+ self.assertTrue(isinstance(ciphers, list))
+ for cipher in ciphers:
+ self.assertTrue(isinstance(cipher, str))
+
+
+
+class ConnectionSendTests(TestCase, _LoopbackMixin):
+ """
+ Tests for :py:obj:`Connection.send`
+ """
+ def test_wrong_args(self):
+ """
+ When called with arguments other than string argument for its first
+ parameter or more than two arguments, :py:obj:`Connection.send` raises
+ :py:obj:`TypeError`.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.send)
+ self.assertRaises(TypeError, connection.send, object())
+ self.assertRaises(TypeError, connection.send, "foo", object(), "bar")
+
+
+ def test_short_bytes(self):
+ """
+ When passed a short byte string, :py:obj:`Connection.send` transmits all of it
+ and returns the number of bytes sent.
+ """
+ server, client = self._loopback()
+ count = server.send(b('xy'))
+ self.assertEquals(count, 2)
+ self.assertEquals(client.recv(2), b('xy'))
+
+
+ def test_text(self):
+ """
+ When passed a text, :py:obj:`Connection.send` transmits all of it and
+ returns the number of bytes sent. It also raises a DeprecationWarning.
+ """
+ server, client = self._loopback()
+ with catch_warnings(record=True) as w:
+ simplefilter("always")
+ count = server.send(b"xy".decode("ascii"))
+ self.assertEqual(
+ "{0} for buf is no longer accepted, use bytes".format(
+ WARNING_TYPE_EXPECTED
+ ),
+ str(w[-1].message)
+ )
+ self.assertIs(w[-1].category, DeprecationWarning)
+ self.assertEquals(count, 2)
+ self.assertEquals(client.recv(2), b"xy")
+
+ try:
+ memoryview
+ except NameError:
+ "cannot test sending memoryview without memoryview"
+ else:
+ def test_short_memoryview(self):
+ """
+ When passed a memoryview onto a small number of bytes,
+ :py:obj:`Connection.send` transmits all of them and returns the number of
+ bytes sent.
+ """
+ server, client = self._loopback()
+ count = server.send(memoryview(b('xy')))
+ self.assertEquals(count, 2)
+ self.assertEquals(client.recv(2), b('xy'))
+
+
+ try:
+ buffer
+ except NameError:
+ "cannot test sending buffer without buffer"
+ else:
+ def test_short_buffer(self):
+ """
+ When passed a buffer containing a small number of bytes,
+ :py:obj:`Connection.send` transmits all of them and returns the number of
+ bytes sent.
+ """
+ server, client = self._loopback()
+ count = server.send(buffer(b('xy')))
+ self.assertEquals(count, 2)
+ self.assertEquals(client.recv(2), b('xy'))
+
+
+
+def _make_memoryview(size):
+ """
+ Create a new ``memoryview`` wrapped around a ``bytearray`` of the given
+ size.
+ """
+ return memoryview(bytearray(size))
+
+
+
+class ConnectionRecvIntoTests(TestCase, _LoopbackMixin):
+ """
+ Tests for :py:obj:`Connection.recv_into`
+ """
+ def _no_length_test(self, factory):
+ """
+ Assert that when the given buffer is passed to
+ ``Connection.recv_into``, whatever bytes are available to be received
+ that fit into that buffer are written into that buffer.
+ """
+ output_buffer = factory(5)
+
+ server, client = self._loopback()
+ server.send(b('xy'))
+
+ self.assertEqual(client.recv_into(output_buffer), 2)
+ self.assertEqual(output_buffer, bytearray(b('xy\x00\x00\x00')))
+
+
+ def test_bytearray_no_length(self):
+ """
+ :py:obj:`Connection.recv_into` can be passed a ``bytearray`` instance
+ and data in the receive buffer is written to it.
+ """
+ self._no_length_test(bytearray)
+
+
+ def _respects_length_test(self, factory):
+ """
+ Assert that when the given buffer is passed to ``Connection.recv_into``
+ along with a value for ``nbytes`` that is less than the size of that
+ buffer, only ``nbytes`` bytes are written into the buffer.
+ """
+ output_buffer = factory(10)
+
+ server, client = self._loopback()
+ server.send(b('abcdefghij'))
+
+ self.assertEqual(client.recv_into(output_buffer, 5), 5)
+ self.assertEqual(
+ output_buffer, bytearray(b('abcde\x00\x00\x00\x00\x00'))
+ )
+
+
+ def test_bytearray_respects_length(self):
+ """
+ When called with a ``bytearray`` instance,
+ :py:obj:`Connection.recv_into` respects the ``nbytes`` parameter and
+ doesn't copy in more than that number of bytes.
+ """
+ self._respects_length_test(bytearray)
+
+
+ def _doesnt_overfill_test(self, factory):
+ """
+ Assert that if there are more bytes available to be read from the
+ receive buffer than would fit into the buffer passed to
+ :py:obj:`Connection.recv_into`, only as many as fit are written into
+ it.
+ """
+ output_buffer = factory(5)
+
+ server, client = self._loopback()
+ server.send(b('abcdefghij'))
+
+ self.assertEqual(client.recv_into(output_buffer), 5)
+ self.assertEqual(output_buffer, bytearray(b('abcde')))
+ rest = client.recv(5)
+ self.assertEqual(b('fghij'), rest)
+
+
+ def test_bytearray_doesnt_overfill(self):
+ """
+ When called with a ``bytearray`` instance,
+ :py:obj:`Connection.recv_into` respects the size of the array and
+ doesn't write more bytes into it than will fit.
+ """
+ self._doesnt_overfill_test(bytearray)
+
+
+ def _really_doesnt_overfill_test(self, factory):
+ """
+ Assert that if the value given by ``nbytes`` is greater than the actual
+ size of the output buffer passed to :py:obj:`Connection.recv_into`, the
+ behavior is as if no value was given for ``nbytes`` at all.
+ """
+ output_buffer = factory(5)
+
+ server, client = self._loopback()
+ server.send(b('abcdefghij'))
+
+ self.assertEqual(client.recv_into(output_buffer, 50), 5)
+ self.assertEqual(output_buffer, bytearray(b('abcde')))
+ rest = client.recv(5)
+ self.assertEqual(b('fghij'), rest)
+
+
+ def test_bytearray_really_doesnt_overfill(self):
+ """
+ When called with a ``bytearray`` instance and an ``nbytes`` value that
+ is too large, :py:obj:`Connection.recv_into` respects the size of the
+ array and not the ``nbytes`` value and doesn't write more bytes into
+ the buffer than will fit.
+ """
+ self._doesnt_overfill_test(bytearray)
+
+
+ try:
+ memoryview
+ except NameError:
+ "cannot test recv_into memoryview without memoryview"
+ else:
+ def test_memoryview_no_length(self):
+ """
+ :py:obj:`Connection.recv_into` can be passed a ``memoryview``
+ instance and data in the receive buffer is written to it.
+ """
+ self._no_length_test(_make_memoryview)
+
+
+ def test_memoryview_respects_length(self):
+ """
+ When called with a ``memoryview`` instance,
+ :py:obj:`Connection.recv_into` respects the ``nbytes`` parameter
+ and doesn't copy more than that number of bytes in.
+ """
+ self._respects_length_test(_make_memoryview)
+
+
+ def test_memoryview_doesnt_overfill(self):
+ """
+ When called with a ``memoryview`` instance,
+ :py:obj:`Connection.recv_into` respects the size of the array and
+ doesn't write more bytes into it than will fit.
+ """
+ self._doesnt_overfill_test(_make_memoryview)
+
+
+ def test_memoryview_really_doesnt_overfill(self):
+ """
+ When called with a ``memoryview`` instance and an ``nbytes`` value
+ that is too large, :py:obj:`Connection.recv_into` respects the size
+ of the array and not the ``nbytes`` value and doesn't write more
+ bytes into the buffer than will fit.
+ """
+ self._doesnt_overfill_test(_make_memoryview)
+
+
+
+class ConnectionSendallTests(TestCase, _LoopbackMixin):
+ """
+ Tests for :py:obj:`Connection.sendall`.
+ """
+ def test_wrong_args(self):
+ """
+ When called with arguments other than a string argument for its first
+ parameter or with more than two arguments, :py:obj:`Connection.sendall`
+ raises :py:obj:`TypeError`.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.sendall)
+ self.assertRaises(TypeError, connection.sendall, object())
+ self.assertRaises(
+ TypeError, connection.sendall, "foo", object(), "bar")
+
+
+ def test_short(self):
+ """
+ :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to
+ it.
+ """
+ server, client = self._loopback()
+ server.sendall(b('x'))
+ self.assertEquals(client.recv(1), b('x'))
+
+
+ def test_text(self):
+ """
+ :py:obj:`Connection.sendall` transmits all the content in the string
+ passed to it raising a DeprecationWarning in case of this being a text.
+ """
+ server, client = self._loopback()
+ with catch_warnings(record=True) as w:
+ simplefilter("always")
+ server.sendall(b"x".decode("ascii"))
+ self.assertEqual(
+ "{0} for buf is no longer accepted, use bytes".format(
+ WARNING_TYPE_EXPECTED
+ ),
+ str(w[-1].message)
+ )
+ self.assertIs(w[-1].category, DeprecationWarning)
+ self.assertEquals(client.recv(1), b"x")
+
+
+ try:
+ memoryview
+ except NameError:
+ "cannot test sending memoryview without memoryview"
+ else:
+ def test_short_memoryview(self):
+ """
+ When passed a memoryview onto a small number of bytes,
+ :py:obj:`Connection.sendall` transmits all of them.
+ """
+ server, client = self._loopback()
+ server.sendall(memoryview(b('x')))
+ self.assertEquals(client.recv(1), b('x'))
+
+
+ try:
+ buffer
+ except NameError:
+ "cannot test sending buffers without buffers"
+ else:
+ def test_short_buffers(self):
+ """
+ When passed a buffer containing a small number of bytes,
+ :py:obj:`Connection.sendall` transmits all of them.
+ """
+ server, client = self._loopback()
+ server.sendall(buffer(b('x')))
+ self.assertEquals(client.recv(1), b('x'))
+
+
+ def test_long(self):
+ """
+ :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to
+ it even if this requires multiple calls of an underlying write function.
+ """
+ server, client = self._loopback()
+ # Should be enough, underlying SSL_write should only do 16k at a time.
+ # On Windows, after 32k of bytes the write will block (forever - because
+ # no one is yet reading).
+ message = b('x') * (1024 * 32 - 1) + b('y')
+ server.sendall(message)
+ accum = []
+ received = 0
+ while received < len(message):
+ data = client.recv(1024)
+ accum.append(data)
+ received += len(data)
+ self.assertEquals(message, b('').join(accum))
+
+
+ def test_closed(self):
+ """
+ If the underlying socket is closed, :py:obj:`Connection.sendall` propagates the
+ write error from the low level write call.
+ """
+ server, client = self._loopback()
+ server.sock_shutdown(2)
+ exc = self.assertRaises(SysCallError, server.sendall, b"hello, world")
+ if platform == "win32":
+ self.assertEqual(exc.args[0], ESHUTDOWN)
+ else:
+ self.assertEqual(exc.args[0], EPIPE)
+
+
+
+class ConnectionRenegotiateTests(TestCase, _LoopbackMixin):
+ """
+ Tests for SSL renegotiation APIs.
+ """
+ def test_renegotiate_wrong_args(self):
+ """
+ :py:obj:`Connection.renegotiate` raises :py:obj:`TypeError` if called with any
+ arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.renegotiate, None)
+
+
+ def test_total_renegotiations_wrong_args(self):
+ """
+ :py:obj:`Connection.total_renegotiations` raises :py:obj:`TypeError` if called with
+ any arguments.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertRaises(TypeError, connection.total_renegotiations, None)
+
+
+ def test_total_renegotiations(self):
+ """
+ :py:obj:`Connection.total_renegotiations` returns :py:obj:`0` before any
+ renegotiations have happened.
+ """
+ connection = Connection(Context(TLSv1_METHOD), None)
+ self.assertEquals(connection.total_renegotiations(), 0)
+
+
+# def test_renegotiate(self):
+# """
+# """
+# server, client = self._loopback()
+
+# server.send("hello world")
+# self.assertEquals(client.recv(len("hello world")), "hello world")
+
+# self.assertEquals(server.total_renegotiations(), 0)
+# self.assertTrue(server.renegotiate())
+
+# server.setblocking(False)
+# client.setblocking(False)
+# while server.renegotiate_pending():
+# client.do_handshake()
+# server.do_handshake()
+
+# self.assertEquals(server.total_renegotiations(), 1)
+
+
+
+
+class ErrorTests(TestCase):
+ """
+ Unit tests for :py:obj:`OpenSSL.SSL.Error`.
+ """
+ def test_type(self):
+ """
+ :py:obj:`Error` is an exception type.
+ """
+ self.assertTrue(issubclass(Error, Exception))
+ self.assertEqual(Error.__name__, 'Error')
+
+
+
+class ConstantsTests(TestCase):
+ """
+ Tests for the values of constants exposed in :py:obj:`OpenSSL.SSL`.
+
+ These are values defined by OpenSSL intended only to be used as flags to
+ OpenSSL APIs. The only assertions it seems can be made about them is
+ their values.
+ """
+ # unittest.TestCase has no skip mechanism
+ if OP_NO_QUERY_MTU is not None:
+ def test_op_no_query_mtu(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.OP_NO_QUERY_MTU` is 0x1000, the value of
+ :py:const:`SSL_OP_NO_QUERY_MTU` defined by :file:`openssl/ssl.h`.
+ """
+ self.assertEqual(OP_NO_QUERY_MTU, 0x1000)
+ else:
+ "OP_NO_QUERY_MTU unavailable - OpenSSL version may be too old"
+
+
+ if OP_COOKIE_EXCHANGE is not None:
+ def test_op_cookie_exchange(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.OP_COOKIE_EXCHANGE` is 0x2000, the value
+ of :py:const:`SSL_OP_COOKIE_EXCHANGE` defined by :file:`openssl/ssl.h`.
+ """
+ self.assertEqual(OP_COOKIE_EXCHANGE, 0x2000)
+ else:
+ "OP_COOKIE_EXCHANGE unavailable - OpenSSL version may be too old"
+
+
+ if OP_NO_TICKET is not None:
+ def test_op_no_ticket(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.OP_NO_TICKET` is 0x4000, the value of
+ :py:const:`SSL_OP_NO_TICKET` defined by :file:`openssl/ssl.h`.
+ """
+ self.assertEqual(OP_NO_TICKET, 0x4000)
+ else:
+ "OP_NO_TICKET unavailable - OpenSSL version may be too old"
+
+
+ if OP_NO_COMPRESSION is not None:
+ def test_op_no_compression(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.OP_NO_COMPRESSION` is 0x20000, the value
+ of :py:const:`SSL_OP_NO_COMPRESSION` defined by :file:`openssl/ssl.h`.
+ """
+ self.assertEqual(OP_NO_COMPRESSION, 0x20000)
+ else:
+ "OP_NO_COMPRESSION unavailable - OpenSSL version may be too old"
+
+
+ def test_sess_cache_off(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_OFF` 0x0, the value of
+ :py:obj:`SSL_SESS_CACHE_OFF` defined by ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x0, SESS_CACHE_OFF)
+
+
+ def test_sess_cache_client(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_CLIENT` 0x1, the value of
+ :py:obj:`SSL_SESS_CACHE_CLIENT` defined by ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x1, SESS_CACHE_CLIENT)
+
+
+ def test_sess_cache_server(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_SERVER` 0x2, the value of
+ :py:obj:`SSL_SESS_CACHE_SERVER` defined by ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x2, SESS_CACHE_SERVER)
+
+
+ def test_sess_cache_both(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_BOTH` 0x3, the value of
+ :py:obj:`SSL_SESS_CACHE_BOTH` defined by ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x3, SESS_CACHE_BOTH)
+
+
+ def test_sess_cache_no_auto_clear(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_AUTO_CLEAR` 0x80, the
+ value of :py:obj:`SSL_SESS_CACHE_NO_AUTO_CLEAR` defined by
+ ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x80, SESS_CACHE_NO_AUTO_CLEAR)
+
+
+ def test_sess_cache_no_internal_lookup(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_LOOKUP` 0x100,
+ the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_LOOKUP` defined by
+ ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x100, SESS_CACHE_NO_INTERNAL_LOOKUP)
+
+
+ def test_sess_cache_no_internal_store(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_STORE` 0x200,
+ the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_STORE` defined by
+ ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x200, SESS_CACHE_NO_INTERNAL_STORE)
+
+
+ def test_sess_cache_no_internal(self):
+ """
+ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL` 0x300, the
+ value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL` defined by
+ ``openssl/ssl.h``.
+ """
+ self.assertEqual(0x300, SESS_CACHE_NO_INTERNAL)
+
+
+
+class MemoryBIOTests(TestCase, _LoopbackMixin):
+ """
+ Tests for :py:obj:`OpenSSL.SSL.Connection` using a memory BIO.
+ """
+ def _server(self, sock):
+ """
+ Create a new server-side SSL :py:obj:`Connection` object wrapped around
+ :py:obj:`sock`.
+ """
+ # Create the server side Connection. This is mostly setup boilerplate
+ # - use TLSv1, use a particular certificate, etc.
+ server_ctx = Context(TLSv1_METHOD)
+ server_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE )
+ server_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb)
+ server_store = server_ctx.get_cert_store()
+ server_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+ server_ctx.check_privatekey()
+ server_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem))
+ # Here the Connection is actually created. If None is passed as the 2nd
+ # parameter, it indicates a memory BIO should be created.
+ server_conn = Connection(server_ctx, sock)
+ server_conn.set_accept_state()
+ return server_conn
+
+
+ def _client(self, sock):
+ """
+ Create a new client-side SSL :py:obj:`Connection` object wrapped around
+ :py:obj:`sock`.
+ """
+ # Now create the client side Connection. Similar boilerplate to the
+ # above.
+ client_ctx = Context(TLSv1_METHOD)
+ client_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE )
+ client_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb)
+ client_store = client_ctx.get_cert_store()
+ client_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, client_key_pem))
+ client_ctx.use_certificate(load_certificate(FILETYPE_PEM, client_cert_pem))
+ client_ctx.check_privatekey()
+ client_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem))
+ client_conn = Connection(client_ctx, sock)
+ client_conn.set_connect_state()
+ return client_conn
+
+
+ def test_memoryConnect(self):
+ """
+ Two :py:obj:`Connection`s which use memory BIOs can be manually connected by
+ reading from the output of each and writing those bytes to the input of
+ the other and in this way establish a connection and exchange
+ application-level bytes with each other.
+ """
+ server_conn = self._server(None)
+ client_conn = self._client(None)
+
+ # There should be no key or nonces yet.
+ self.assertIdentical(server_conn.master_key(), None)
+ self.assertIdentical(server_conn.client_random(), None)
+ self.assertIdentical(server_conn.server_random(), None)
+
+ # First, the handshake needs to happen. We'll deliver bytes back and
+ # forth between the client and server until neither of them feels like
+ # speaking any more.
+ self.assertIdentical(
+ self._interactInMemory(client_conn, server_conn), None)
+
+ # Now that the handshake is done, there should be a key and nonces.
+ self.assertNotIdentical(server_conn.master_key(), None)
+ self.assertNotIdentical(server_conn.client_random(), None)
+ self.assertNotIdentical(server_conn.server_random(), None)
+ self.assertEquals(server_conn.client_random(), client_conn.client_random())
+ self.assertEquals(server_conn.server_random(), client_conn.server_random())
+ self.assertNotEquals(server_conn.client_random(), server_conn.server_random())
+ self.assertNotEquals(client_conn.client_random(), client_conn.server_random())
+
+ # Here are the bytes we'll try to send.
+ important_message = b('One if by land, two if by sea.')
+
+ server_conn.write(important_message)
+ self.assertEquals(
+ self._interactInMemory(client_conn, server_conn),
+ (client_conn, important_message))
+
+ client_conn.write(important_message[::-1])
+ self.assertEquals(
+ self._interactInMemory(client_conn, server_conn),
+ (server_conn, important_message[::-1]))
+
+
+ def test_socketConnect(self):
+ """
+ Just like :py:obj:`test_memoryConnect` but with an actual socket.
+
+ This is primarily to rule out the memory BIO code as the source of
+ any problems encountered while passing data over a :py:obj:`Connection` (if
+ this test fails, there must be a problem outside the memory BIO
+ code, as no memory BIO is involved here). Even though this isn't a
+ memory BIO test, it's convenient to have it here.
+ """
+ server_conn, client_conn = self._loopback()
+
+ important_message = b("Help me Obi Wan Kenobi, you're my only hope.")
+ client_conn.send(important_message)
+ msg = server_conn.recv(1024)
+ self.assertEqual(msg, important_message)
+
+ # Again in the other direction, just for fun.
+ important_message = important_message[::-1]
+ server_conn.send(important_message)
+ msg = client_conn.recv(1024)
+ self.assertEqual(msg, important_message)
+
+
+ def test_socketOverridesMemory(self):
+ """
+ Test that :py:obj:`OpenSSL.SSL.bio_read` and :py:obj:`OpenSSL.SSL.bio_write` don't
+ work on :py:obj:`OpenSSL.SSL.Connection`() that use sockets.
+ """
+ context = Context(SSLv3_METHOD)
+ client = socket()
+ clientSSL = Connection(context, client)
+ self.assertRaises( TypeError, clientSSL.bio_read, 100)
+ self.assertRaises( TypeError, clientSSL.bio_write, "foo")
+ self.assertRaises( TypeError, clientSSL.bio_shutdown )
+
+
+ def test_outgoingOverflow(self):
+ """
+ If more bytes than can be written to the memory BIO are passed to
+ :py:obj:`Connection.send` at once, the number of bytes which were written is
+ returned and that many bytes from the beginning of the input can be
+ read from the other end of the connection.
+ """
+ server = self._server(None)
+ client = self._client(None)
+
+ self._interactInMemory(client, server)
+
+ size = 2 ** 15
+ sent = client.send(b"x" * size)
+ # Sanity check. We're trying to test what happens when the entire
+ # input can't be sent. If the entire input was sent, this test is
+ # meaningless.
+ self.assertTrue(sent < size)
+
+ receiver, received = self._interactInMemory(client, server)
+ self.assertIdentical(receiver, server)
+
+ # We can rely on all of these bytes being received at once because
+ # _loopback passes 2 ** 16 to recv - more than 2 ** 15.
+ self.assertEquals(len(received), sent)
+
+
+ def test_shutdown(self):
+ """
+ :py:obj:`Connection.bio_shutdown` signals the end of the data stream from
+ which the :py:obj:`Connection` reads.
+ """
+ server = self._server(None)
+ server.bio_shutdown()
+ e = self.assertRaises(Error, server.recv, 1024)
+ # We don't want WantReadError or ZeroReturnError or anything - it's a
+ # handshake failure.
+ self.assertEquals(e.__class__, Error)
+
+
+ def test_unexpectedEndOfFile(self):
+ """
+ If the connection is lost before an orderly SSL shutdown occurs,
+ :py:obj:`OpenSSL.SSL.SysCallError` is raised with a message of
+ "Unexpected EOF".
+ """
+ server_conn, client_conn = self._loopback()
+ client_conn.sock_shutdown(SHUT_RDWR)
+ exc = self.assertRaises(SysCallError, server_conn.recv, 1024)
+ self.assertEqual(exc.args, (-1, "Unexpected EOF"))
+
+
+ def _check_client_ca_list(self, func):
+ """
+ Verify the return value of the :py:obj:`get_client_ca_list` method for server and client connections.
+
+ :param func: A function which will be called with the server context
+ before the client and server are connected to each other. This
+ function should specify a list of CAs for the server to send to the
+ client and return that same list. The list will be used to verify
+ that :py:obj:`get_client_ca_list` returns the proper value at various
+ times.
+ """
+ server = self._server(None)
+ client = self._client(None)
+ self.assertEqual(client.get_client_ca_list(), [])
+ self.assertEqual(server.get_client_ca_list(), [])
+ ctx = server.get_context()
+ expected = func(ctx)
+ self.assertEqual(client.get_client_ca_list(), [])
+ self.assertEqual(server.get_client_ca_list(), expected)
+ self._interactInMemory(client, server)
+ self.assertEqual(client.get_client_ca_list(), expected)
+ self.assertEqual(server.get_client_ca_list(), expected)
+
+
+ def test_set_client_ca_list_errors(self):
+ """
+ :py:obj:`Context.set_client_ca_list` raises a :py:obj:`TypeError` if called with a
+ non-list or a list that contains objects other than X509Names.
+ """
+ ctx = Context(TLSv1_METHOD)
+ self.assertRaises(TypeError, ctx.set_client_ca_list, "spam")
+ self.assertRaises(TypeError, ctx.set_client_ca_list, ["spam"])
+ self.assertIdentical(ctx.set_client_ca_list([]), None)
+
+
+ def test_set_empty_ca_list(self):
+ """
+ If passed an empty list, :py:obj:`Context.set_client_ca_list` configures the
+ context to send no CA names to the client and, on both the server and
+ client sides, :py:obj:`Connection.get_client_ca_list` returns an empty list
+ after the connection is set up.
+ """
+ def no_ca(ctx):
+ ctx.set_client_ca_list([])
+ return []
+ self._check_client_ca_list(no_ca)
+
+
+ def test_set_one_ca_list(self):
+ """
+ If passed a list containing a single X509Name,
+ :py:obj:`Context.set_client_ca_list` configures the context to send that CA
+ name to the client and, on both the server and client sides,
+ :py:obj:`Connection.get_client_ca_list` returns a list containing that
+ X509Name after the connection is set up.
+ """
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ cadesc = cacert.get_subject()
+ def single_ca(ctx):
+ ctx.set_client_ca_list([cadesc])
+ return [cadesc]
+ self._check_client_ca_list(single_ca)
+
+
+ def test_set_multiple_ca_list(self):
+ """
+ If passed a list containing multiple X509Name objects,
+ :py:obj:`Context.set_client_ca_list` configures the context to send those CA
+ names to the client and, on both the server and client sides,
+ :py:obj:`Connection.get_client_ca_list` returns a list containing those
+ X509Names after the connection is set up.
+ """
+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
+ clcert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ sedesc = secert.get_subject()
+ cldesc = clcert.get_subject()
+
+ def multiple_ca(ctx):
+ L = [sedesc, cldesc]
+ ctx.set_client_ca_list(L)
+ return L
+ self._check_client_ca_list(multiple_ca)
+
+
+ def test_reset_ca_list(self):
+ """
+ If called multiple times, only the X509Names passed to the final call
+ of :py:obj:`Context.set_client_ca_list` are used to configure the CA names
+ sent to the client.
+ """
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
+ clcert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ cadesc = cacert.get_subject()
+ sedesc = secert.get_subject()
+ cldesc = clcert.get_subject()
+
+ def changed_ca(ctx):
+ ctx.set_client_ca_list([sedesc, cldesc])
+ ctx.set_client_ca_list([cadesc])
+ return [cadesc]
+ self._check_client_ca_list(changed_ca)
+
+
+ def test_mutated_ca_list(self):
+ """
+ If the list passed to :py:obj:`Context.set_client_ca_list` is mutated
+ afterwards, this does not affect the list of CA names sent to the
+ client.
+ """
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ cadesc = cacert.get_subject()
+ sedesc = secert.get_subject()
+
+ def mutated_ca(ctx):
+ L = [cadesc]
+ ctx.set_client_ca_list([cadesc])
+ L.append(sedesc)
+ return [cadesc]
+ self._check_client_ca_list(mutated_ca)
+
+
+ def test_add_client_ca_errors(self):
+ """
+ :py:obj:`Context.add_client_ca` raises :py:obj:`TypeError` if called with a non-X509
+ object or with a number of arguments other than one.
+ """
+ ctx = Context(TLSv1_METHOD)
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ self.assertRaises(TypeError, ctx.add_client_ca)
+ self.assertRaises(TypeError, ctx.add_client_ca, "spam")
+ self.assertRaises(TypeError, ctx.add_client_ca, cacert, cacert)
+
+
+ def test_one_add_client_ca(self):
+ """
+ A certificate's subject can be added as a CA to be sent to the client
+ with :py:obj:`Context.add_client_ca`.
+ """
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ cadesc = cacert.get_subject()
+ def single_ca(ctx):
+ ctx.add_client_ca(cacert)
+ return [cadesc]
+ self._check_client_ca_list(single_ca)
+
+
+ def test_multiple_add_client_ca(self):
+ """
+ Multiple CA names can be sent to the client by calling
+ :py:obj:`Context.add_client_ca` with multiple X509 objects.
+ """
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ cadesc = cacert.get_subject()
+ sedesc = secert.get_subject()
+
+ def multiple_ca(ctx):
+ ctx.add_client_ca(cacert)
+ ctx.add_client_ca(secert)
+ return [cadesc, sedesc]
+ self._check_client_ca_list(multiple_ca)
+
+
+ def test_set_and_add_client_ca(self):
+ """
+ A call to :py:obj:`Context.set_client_ca_list` followed by a call to
+ :py:obj:`Context.add_client_ca` results in using the CA names from the first
+ call and the CA name from the second call.
+ """
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
+ clcert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ cadesc = cacert.get_subject()
+ sedesc = secert.get_subject()
+ cldesc = clcert.get_subject()
+
+ def mixed_set_add_ca(ctx):
+ ctx.set_client_ca_list([cadesc, sedesc])
+ ctx.add_client_ca(clcert)
+ return [cadesc, sedesc, cldesc]
+ self._check_client_ca_list(mixed_set_add_ca)
+
+
+ def test_set_after_add_client_ca(self):
+ """
+ A call to :py:obj:`Context.set_client_ca_list` after a call to
+ :py:obj:`Context.add_client_ca` replaces the CA name specified by the former
+ call with the names specified by the latter cal.
+ """
+ cacert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ secert = load_certificate(FILETYPE_PEM, server_cert_pem)
+ clcert = load_certificate(FILETYPE_PEM, server_cert_pem)
+
+ cadesc = cacert.get_subject()
+ sedesc = secert.get_subject()
+
+ def set_replaces_add_ca(ctx):
+ ctx.add_client_ca(clcert)
+ ctx.set_client_ca_list([cadesc])
+ ctx.add_client_ca(secert)
+ return [cadesc, sedesc]
+ self._check_client_ca_list(set_replaces_add_ca)
+
+
+
+class ConnectionBIOTests(TestCase):
+ """
+ Tests for :py:obj:`Connection.bio_read` and :py:obj:`Connection.bio_write`.
+ """
+ def test_wantReadError(self):
+ """
+ :py:obj:`Connection.bio_read` raises :py:obj:`OpenSSL.SSL.WantReadError`
+ if there are no bytes available to be read from the BIO.
+ """
+ ctx = Context(TLSv1_METHOD)
+ conn = Connection(ctx, None)
+ self.assertRaises(WantReadError, conn.bio_read, 1024)
+
+
+ def test_buffer_size(self):
+ """
+ :py:obj:`Connection.bio_read` accepts an integer giving the maximum
+ number of bytes to read and return.
+ """
+ ctx = Context(TLSv1_METHOD)
+ conn = Connection(ctx, None)
+ conn.set_connect_state()
+ try:
+ conn.do_handshake()
+ except WantReadError:
+ pass
+ data = conn.bio_read(2)
+ self.assertEqual(2, len(data))
+
+
+ if not PY3:
+ def test_buffer_size_long(self):
+ """
+ On Python 2 :py:obj:`Connection.bio_read` accepts values of type
+ :py:obj:`long` as well as :py:obj:`int`.
+ """
+ ctx = Context(TLSv1_METHOD)
+ conn = Connection(ctx, None)
+ conn.set_connect_state()
+ try:
+ conn.do_handshake()
+ except WantReadError:
+ pass
+ data = conn.bio_read(long(2))
+ self.assertEqual(2, len(data))
+
+
+
+
+class InfoConstantTests(TestCase):
+ """
+ Tests for assorted constants exposed for use in info callbacks.
+ """
+ def test_integers(self):
+ """
+ All of the info constants are integers.
+
+ This is a very weak test. It would be nice to have one that actually
+ verifies that as certain info events happen, the value passed to the
+ info callback matches up with the constant exposed by OpenSSL.SSL.
+ """
+ for const in [
+ SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_ST_INIT,
+ SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE,
+ SSL_CB_LOOP, SSL_CB_EXIT, SSL_CB_READ, SSL_CB_WRITE, SSL_CB_ALERT,
+ SSL_CB_READ_ALERT, SSL_CB_WRITE_ALERT, SSL_CB_ACCEPT_LOOP,
+ SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_CONNECT_EXIT,
+ SSL_CB_HANDSHAKE_START, SSL_CB_HANDSHAKE_DONE]:
+
+ self.assertTrue(isinstance(const, int))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/Python/Lib/OpenSSL/test/test_tsafe.py b/lib/Python/Lib/OpenSSL/test/test_tsafe.py
new file mode 100644
index 000000000..04569574d
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/test_tsafe.py
@@ -0,0 +1,24 @@
+# Copyright (C) Jean-Paul Calderone
+# See LICENSE for details.
+
+"""
+Unit tests for :py:obj:`OpenSSL.tsafe`.
+"""
+
+from OpenSSL.SSL import TLSv1_METHOD, Context
+from OpenSSL.tsafe import Connection
+from OpenSSL.test.util import TestCase
+
+
+class ConnectionTest(TestCase):
+ """
+ Tests for :py:obj:`OpenSSL.tsafe.Connection`.
+ """
+ def test_instantiation(self):
+ """
+ :py:obj:`OpenSSL.tsafe.Connection` can be instantiated.
+ """
+ # The following line should not throw an error. This isn't an ideal
+ # test. It would be great to refactor the other Connection tests so
+ # they could automatically be applied to this class too.
+ Connection(Context(TLSv1_METHOD), None)
diff --git a/lib/Python/Lib/OpenSSL/test/test_util.py b/lib/Python/Lib/OpenSSL/test/test_util.py
new file mode 100644
index 000000000..8d92a3c75
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/test_util.py
@@ -0,0 +1,17 @@
+from OpenSSL._util import exception_from_error_queue, lib
+from OpenSSL.test.util import TestCase
+
+
+
+class ErrorTests(TestCase):
+ """
+ Tests for handling of certain OpenSSL error cases.
+ """
+ def test_exception_from_error_queue_nonexistent_reason(self):
+ """
+ :py:func:`exception_from_error_queue` raises ``ValueError`` when it
+ encounters an OpenSSL error code which does not have a reason string.
+ """
+ lib.ERR_put_error(lib.ERR_LIB_EVP, 0, 1112, b"", 10)
+ exc = self.assertRaises(ValueError, exception_from_error_queue, ValueError)
+ self.assertEqual(exc.args[0][0][2], "")
diff --git a/lib/Python/Lib/OpenSSL/test/util.py b/lib/Python/Lib/OpenSSL/test/util.py
new file mode 100644
index 000000000..b8be91deb
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/test/util.py
@@ -0,0 +1,463 @@
+# Copyright (C) Jean-Paul Calderone
+# Copyright (C) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Helpers for the OpenSSL test suite, largely copied from
+U{Twisted<http://twistedmatrix.com/>}.
+"""
+
+import shutil
+import traceback
+import os, os.path
+from tempfile import mktemp
+from unittest import TestCase
+import sys
+
+from six import PY3
+
+from OpenSSL._util import exception_from_error_queue
+from OpenSSL.crypto import Error
+
+try:
+ import memdbg
+except Exception:
+ class _memdbg(object): heap = None
+ memdbg = _memdbg()
+
+from OpenSSL._util import ffi, lib, byte_string as b
+
+
+# This is the UTF-8 encoding of the SNOWMAN unicode code point.
+NON_ASCII = b("\xe2\x98\x83").decode("utf-8")
+
+
+class TestCase(TestCase):
+ """
+ :py:class:`TestCase` adds useful testing functionality beyond what is available
+ from the standard library :py:class:`unittest.TestCase`.
+ """
+ def run(self, result):
+ run = super(TestCase, self).run
+ if memdbg.heap is None:
+ return run(result)
+
+ # Run the test as usual
+ before = set(memdbg.heap)
+ run(result)
+
+ # Clean up some long-lived allocations so they won't be reported as
+ # memory leaks.
+ lib.CRYPTO_cleanup_all_ex_data()
+ lib.ERR_remove_thread_state(ffi.NULL)
+ after = set(memdbg.heap)
+
+ if not after - before:
+ # No leaks, fast succeed
+ return
+
+ if result.wasSuccessful():
+ # If it passed, run it again with memory debugging
+ before = set(memdbg.heap)
+ run(result)
+
+ # Clean up some long-lived allocations so they won't be reported as
+ # memory leaks.
+ lib.CRYPTO_cleanup_all_ex_data()
+ lib.ERR_remove_thread_state(ffi.NULL)
+
+ after = set(memdbg.heap)
+
+ self._reportLeaks(after - before, result)
+
+
+ def _reportLeaks(self, leaks, result):
+ def format_leak(p):
+ stacks = memdbg.heap[p]
+ # Eventually look at multiple stacks for the realloc() case. For
+ # now just look at the original allocation location.
+ (size, python_stack, c_stack) = stacks[0]
+
+ stack = traceback.format_list(python_stack)[:-1]
+
+ # c_stack looks something like this (interesting parts indicated
+ # with inserted arrows not part of the data):
+ #
+ # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf]
+ # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
+ # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
+ # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
+ # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
+ # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129]
+ # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
+ # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726]
+ # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51]
+ # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6]
+ # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec]
+ # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e]
+ # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819]
+ # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c]
+ # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting
+ # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] .
+ # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] .
+ # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] .
+ # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] .
+ # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting
+ # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397]
+ # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a]
+ # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52]
+ # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e]
+ # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419]
+ # ...
+ #
+ # Notice the stack is upside down compared to a Python traceback.
+ # Identify the start and end of interesting bits and stuff it into the stack we report.
+
+ saved = list(c_stack)
+
+ # Figure the first interesting frame will be after a the cffi-compiled module
+ while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]:
+ c_stack.pop()
+
+ # Figure the last interesting frame will always be CRYPTO_malloc,
+ # since that's where we hooked in to things.
+ while c_stack and 'CRYPTO_malloc' not in c_stack[0] and 'CRYPTO_realloc' not in c_stack[0]:
+ c_stack.pop(0)
+
+ if c_stack:
+ c_stack.reverse()
+ else:
+ c_stack = saved[::-1]
+ stack.extend([frame + "\n" for frame in c_stack])
+
+ stack.insert(0, "Leaked (%s) at:\n")
+ return "".join(stack)
+
+ if leaks:
+ unique_leaks = {}
+ for p in leaks:
+ size = memdbg.heap[p][-1][0]
+ new_leak = format_leak(p)
+ if new_leak not in unique_leaks:
+ unique_leaks[new_leak] = [(size, p)]
+ else:
+ unique_leaks[new_leak].append((size, p))
+ memdbg.free(p)
+
+ for (stack, allocs) in unique_leaks.iteritems():
+ allocs_accum = []
+ for (size, pointer) in allocs:
+
+ addr = int(ffi.cast('uintptr_t', pointer))
+ allocs_accum.append("%d@0x%x" % (size, addr))
+ allocs_report = ", ".join(sorted(allocs_accum))
+
+ result.addError(
+ self,
+ (None, Exception(stack % (allocs_report,)), None))
+
+
+ def tearDown(self):
+ """
+ Clean up any files or directories created using :py:meth:`TestCase.mktemp`.
+ Subclasses must invoke this method if they override it or the
+ cleanup will not occur.
+ """
+ if False and self._temporaryFiles is not None:
+ for temp in self._temporaryFiles:
+ if os.path.isdir(temp):
+ shutil.rmtree(temp)
+ elif os.path.exists(temp):
+ os.unlink(temp)
+ try:
+ exception_from_error_queue(Error)
+ except Error:
+ e = sys.exc_info()[1]
+ if e.args != ([],):
+ self.fail("Left over errors in OpenSSL error queue: " + repr(e))
+
+
+ def assertIsInstance(self, instance, classOrTuple, message=None):
+ """
+ Fail if C{instance} is not an instance of the given class or of
+ one of the given classes.
+
+ @param instance: the object to test the type (first argument of the
+ C{isinstance} call).
+ @type instance: any.
+ @param classOrTuple: the class or classes to test against (second
+ argument of the C{isinstance} call).
+ @type classOrTuple: class, type, or tuple.
+
+ @param message: Custom text to include in the exception text if the
+ assertion fails.
+ """
+ if not isinstance(instance, classOrTuple):
+ if message is None:
+ suffix = ""
+ else:
+ suffix = ": " + message
+ self.fail("%r is not an instance of %s%s" % (
+ instance, classOrTuple, suffix))
+
+
+ def failUnlessIn(self, containee, container, msg=None):
+ """
+ Fail the test if :py:data:`containee` is not found in :py:data:`container`.
+
+ :param containee: the value that should be in :py:class:`container`
+ :param container: a sequence type, or in the case of a mapping type,
+ will follow semantics of 'if key in dict.keys()'
+ :param msg: if msg is None, then the failure message will be
+ '%r not in %r' % (first, second)
+ """
+ if containee not in container:
+ raise self.failureException(msg or "%r not in %r"
+ % (containee, container))
+ return containee
+ assertIn = failUnlessIn
+
+ def assertNotIn(self, containee, container, msg=None):
+ """
+ Fail the test if C{containee} is found in C{container}.
+
+ @param containee: the value that should not be in C{container}
+ @param container: a sequence type, or in the case of a mapping type,
+ will follow semantics of 'if key in dict.keys()'
+ @param msg: if msg is None, then the failure message will be
+ '%r in %r' % (first, second)
+ """
+ if containee in container:
+ raise self.failureException(msg or "%r in %r"
+ % (containee, container))
+ return containee
+ failIfIn = assertNotIn
+
+
+ def assertIs(self, first, second, msg=None):
+ """
+ Fail the test if :py:data:`first` is not :py:data:`second`. This is an
+ obect-identity-equality test, not an object equality
+ (i.e. :py:func:`__eq__`) test.
+
+ :param msg: if msg is None, then the failure message will be
+ '%r is not %r' % (first, second)
+ """
+ if first is not second:
+ raise self.failureException(msg or '%r is not %r' % (first, second))
+ return first
+ assertIdentical = failUnlessIdentical = assertIs
+
+
+ def assertIsNot(self, first, second, msg=None):
+ """
+ Fail the test if :py:data:`first` is :py:data:`second`. This is an
+ obect-identity-equality test, not an object equality
+ (i.e. :py:func:`__eq__`) test.
+
+ :param msg: if msg is None, then the failure message will be
+ '%r is %r' % (first, second)
+ """
+ if first is second:
+ raise self.failureException(msg or '%r is %r' % (first, second))
+ return first
+ assertNotIdentical = failIfIdentical = assertIsNot
+
+
+ def failUnlessRaises(self, exception, f, *args, **kwargs):
+ """
+ Fail the test unless calling the function :py:data:`f` with the given
+ :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The
+ failure will report the traceback and call stack of the unexpected
+ exception.
+
+ :param exception: exception type that is to be expected
+ :param f: the function to call
+
+ :return: The raised exception instance, if it is of the given type.
+ :raise self.failureException: Raised if the function call does
+ not raise an exception or if it raises an exception of a
+ different type.
+ """
+ try:
+ result = f(*args, **kwargs)
+ except exception:
+ inst = sys.exc_info()[1]
+ return inst
+ except:
+ raise self.failureException('%s raised instead of %s'
+ % (sys.exc_info()[0],
+ exception.__name__,
+ ))
+ else:
+ raise self.failureException('%s not raised (%r returned)'
+ % (exception.__name__, result))
+ assertRaises = failUnlessRaises
+
+
+ _temporaryFiles = None
+ def mktemp(self):
+ """
+ Pathetic substitute for twisted.trial.unittest.TestCase.mktemp.
+ """
+ if self._temporaryFiles is None:
+ self._temporaryFiles = []
+ temp = b(mktemp(dir="."))
+ self._temporaryFiles.append(temp)
+ return temp
+
+
+ # Other stuff
+ def assertConsistentType(self, theType, name, *constructionArgs):
+ """
+ Perform various assertions about :py:data:`theType` to ensure that it is a
+ well-defined type. This is useful for extension types, where it's
+ pretty easy to do something wacky. If something about the type is
+ unusual, an exception will be raised.
+
+ :param theType: The type object about which to make assertions.
+ :param name: A string giving the name of the type.
+ :param constructionArgs: Positional arguments to use with :py:data:`theType` to
+ create an instance of it.
+ """
+ self.assertEqual(theType.__name__, name)
+ self.assertTrue(isinstance(theType, type))
+ instance = theType(*constructionArgs)
+ self.assertIdentical(type(instance), theType)
+
+
+
+class EqualityTestsMixin(object):
+ """
+ A mixin defining tests for the standard implementation of C{==} and C{!=}.
+ """
+ def anInstance(self):
+ """
+ Return an instance of the class under test. Each call to this method
+ must return a different object. All objects returned must be equal to
+ each other.
+ """
+ raise NotImplementedError()
+
+
+ def anotherInstance(self):
+ """
+ Return an instance of the class under test. Each call to this method
+ must return a different object. The objects must not be equal to the
+ objects returned by C{anInstance}. They may or may not be equal to
+ each other (they will not be compared against each other).
+ """
+ raise NotImplementedError()
+
+
+ def test_identicalEq(self):
+ """
+ An object compares equal to itself using the C{==} operator.
+ """
+ o = self.anInstance()
+ self.assertTrue(o == o)
+
+
+ def test_identicalNe(self):
+ """
+ An object doesn't compare not equal to itself using the C{!=} operator.
+ """
+ o = self.anInstance()
+ self.assertFalse(o != o)
+
+
+ def test_sameEq(self):
+ """
+ Two objects that are equal to each other compare equal to each other
+ using the C{==} operator.
+ """
+ a = self.anInstance()
+ b = self.anInstance()
+ self.assertTrue(a == b)
+
+
+ def test_sameNe(self):
+ """
+ Two objects that are equal to each other do not compare not equal to
+ each other using the C{!=} operator.
+ """
+ a = self.anInstance()
+ b = self.anInstance()
+ self.assertFalse(a != b)
+
+
+ def test_differentEq(self):
+ """
+ Two objects that are not equal to each other do not compare equal to
+ each other using the C{==} operator.
+ """
+ a = self.anInstance()
+ b = self.anotherInstance()
+ self.assertFalse(a == b)
+
+
+ def test_differentNe(self):
+ """
+ Two objects that are not equal to each other compare not equal to each
+ other using the C{!=} operator.
+ """
+ a = self.anInstance()
+ b = self.anotherInstance()
+ self.assertTrue(a != b)
+
+
+ def test_anotherTypeEq(self):
+ """
+ The object does not compare equal to an object of an unrelated type
+ (which does not implement the comparison) using the C{==} operator.
+ """
+ a = self.anInstance()
+ b = object()
+ self.assertFalse(a == b)
+
+
+ def test_anotherTypeNe(self):
+ """
+ The object compares not equal to an object of an unrelated type (which
+ does not implement the comparison) using the C{!=} operator.
+ """
+ a = self.anInstance()
+ b = object()
+ self.assertTrue(a != b)
+
+
+ def test_delegatedEq(self):
+ """
+ The result of comparison using C{==} is delegated to the right-hand
+ operand if it is of an unrelated type.
+ """
+ class Delegate(object):
+ def __eq__(self, other):
+ # Do something crazy and obvious.
+ return [self]
+
+ a = self.anInstance()
+ b = Delegate()
+ self.assertEqual(a == b, [b])
+
+
+ def test_delegateNe(self):
+ """
+ The result of comparison using C{!=} is delegated to the right-hand
+ operand if it is of an unrelated type.
+ """
+ class Delegate(object):
+ def __ne__(self, other):
+ # Do something crazy and obvious.
+ return [self]
+
+ a = self.anInstance()
+ b = Delegate()
+ self.assertEqual(a != b, [b])
+
+
+# The type name expected in warnings about using the wrong string type.
+if PY3:
+ WARNING_TYPE_EXPECTED = "str"
+else:
+ WARNING_TYPE_EXPECTED = "unicode"
diff --git a/lib/Python/Lib/OpenSSL/tsafe.py b/lib/Python/Lib/OpenSSL/tsafe.py
new file mode 100644
index 000000000..3a9c71035
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/tsafe.py
@@ -0,0 +1,28 @@
+from OpenSSL import SSL
+_ssl = SSL
+del SSL
+
+import threading
+_RLock = threading.RLock
+del threading
+
+class Connection:
+ def __init__(self, *args):
+ self._ssl_conn = _ssl.Connection(*args)
+ self._lock = _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', 'get_peer_cert_chain', 'want_read',
+ 'want_write', 'set_connect_state', 'set_accept_state',
+ 'connect_ex', 'sendall'):
+ exec("""def %s(self, *args):
+ self._lock.acquire()
+ try:
+ return self._ssl_conn.%s(*args)
+ finally:
+ self._lock.release()\n""" % (f, f))
+
diff --git a/lib/Python/Lib/OpenSSL/version.py b/lib/Python/Lib/OpenSSL/version.py
new file mode 100644
index 000000000..eb3b736e0
--- /dev/null
+++ b/lib/Python/Lib/OpenSSL/version.py
@@ -0,0 +1,9 @@
+# Copyright (C) AB Strakt
+# Copyright (C) Jean-Paul Calderone
+# See LICENSE for details.
+
+"""
+pyOpenSSL - A simple wrapper around the OpenSSL library
+"""
+
+__version__ = '0.15.1'
diff --git a/lib/Python/Lib/PIL/BdfFontFile.py b/lib/Python/Lib/PIL/BdfFontFile.py
new file mode 100644
index 000000000..0c1614e0f
--- /dev/null
+++ b/lib/Python/Lib/PIL/BdfFontFile.py
@@ -0,0 +1,133 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# bitmap distribution font (bdf) file parser
+#
+# history:
+# 1996-05-16 fl created (as bdf2pil)
+# 1997-08-25 fl converted to FontFile driver
+# 2001-05-25 fl removed bogus __init__ call
+# 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev)
+# 2003-04-22 fl more robustification (from Graham Dumpleton)
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1997-2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+from PIL import FontFile
+
+
+# --------------------------------------------------------------------
+# parse X Bitmap Distribution Format (BDF)
+# --------------------------------------------------------------------
+
+bdf_slant = {
+ "R": "Roman",
+ "I": "Italic",
+ "O": "Oblique",
+ "RI": "Reverse Italic",
+ "RO": "Reverse Oblique",
+ "OT": "Other"
+}
+
+bdf_spacing = {
+ "P": "Proportional",
+ "M": "Monospaced",
+ "C": "Cell"
+}
+
+
+def bdf_char(f):
+ # skip to STARTCHAR
+ while True:
+ s = f.readline()
+ if not s:
+ return None
+ if s[:9] == b"STARTCHAR":
+ break
+ id = s[9:].strip().decode('ascii')
+
+ # load symbol properties
+ props = {}
+ while True:
+ s = f.readline()
+ if not s or s[:6] == b"BITMAP":
+ break
+ i = s.find(b" ")
+ props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii')
+
+ # load bitmap
+ bitmap = []
+ while True:
+ s = f.readline()
+ if not s or s[:7] == b"ENDCHAR":
+ break
+ bitmap.append(s[:-1])
+ bitmap = b"".join(bitmap)
+
+ [x, y, l, d] = [int(p) for p in props["BBX"].split()]
+ [dx, dy] = [int(p) for p in props["DWIDTH"].split()]
+
+ bbox = (dx, dy), (l, -d-y, x+l, -d), (0, 0, x, y)
+
+ try:
+ im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
+ except ValueError:
+ # deal with zero-width characters
+ im = Image.new("1", (x, y))
+
+ return id, int(props["ENCODING"]), bbox, im
+
+
+##
+# Font file plugin for the X11 BDF format.
+
+class BdfFontFile(FontFile.FontFile):
+
+ def __init__(self, fp):
+
+ FontFile.FontFile.__init__(self)
+
+ s = fp.readline()
+ if s[:13] != b"STARTFONT 2.1":
+ raise SyntaxError("not a valid BDF file")
+
+ props = {}
+ comments = []
+
+ while True:
+ s = fp.readline()
+ if not s or s[:13] == b"ENDPROPERTIES":
+ break
+ i = s.find(b" ")
+ props[s[:i].decode('ascii')] = s[i+1:-1].decode('ascii')
+ if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
+ if s.find(b"LogicalFontDescription") < 0:
+ comments.append(s[i+1:-1].decode('ascii'))
+
+ font = props["FONT"].split("-")
+
+ font[4] = bdf_slant[font[4].upper()]
+ font[11] = bdf_spacing[font[11].upper()]
+
+ # ascent = int(props["FONT_ASCENT"])
+ # descent = int(props["FONT_DESCENT"])
+
+ # fontname = ";".join(font[1:])
+
+ # print "#", fontname
+ # for i in comments:
+ # print "#", i
+
+ font = []
+ while True:
+ c = bdf_char(fp)
+ if not c:
+ break
+ id, ch, (xy, dst, src), im = c
+ if 0 <= ch < len(self.glyph):
+ self.glyph[ch] = xy, dst, src, im
diff --git a/lib/Python/Lib/PIL/BmpImagePlugin.py b/lib/Python/Lib/PIL/BmpImagePlugin.py
new file mode 100644
index 000000000..30ca10971
--- /dev/null
+++ b/lib/Python/Lib/PIL/BmpImagePlugin.py
@@ -0,0 +1,281 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# BMP file handler
+#
+# Windows (and OS/2) native bitmap storage format.
+#
+# history:
+# 1995-09-01 fl Created
+# 1996-04-30 fl Added save
+# 1997-08-27 fl Fixed save of 1-bit images
+# 1998-03-06 fl Load P images as L where possible
+# 1998-07-03 fl Load P images as 1 where possible
+# 1998-12-29 fl Handle small palettes
+# 2002-12-30 fl Fixed load of 1-bit palette images
+# 2003-04-21 fl Fixed load of 1-bit monochrome images
+# 2003-04-23 fl Added limited support for BI_BITFIELDS compression
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1995-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.7"
+
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+import math
+
+
+i8 = _binary.i8
+i16 = _binary.i16le
+i32 = _binary.i32le
+o8 = _binary.o8
+o16 = _binary.o16le
+o32 = _binary.o32le
+
+#
+# --------------------------------------------------------------------
+# Read BMP file
+
+BIT2MODE = {
+ # bits => mode, rawmode
+ 1: ("P", "P;1"),
+ 4: ("P", "P;4"),
+ 8: ("P", "P"),
+ 16: ("RGB", "BGR;15"),
+ 24: ("RGB", "BGR"),
+ 32: ("RGB", "BGRX"),
+}
+
+
+def _accept(prefix):
+ return prefix[:2] == b"BM"
+
+
+# ==============================================================================
+# Image plugin for the Windows BMP format.
+# ==============================================================================
+class BmpImageFile(ImageFile.ImageFile):
+ """ Image plugin for the Windows Bitmap format (BMP) """
+
+ # -------------------------------------------------------------- Description
+ format_description = "Windows Bitmap"
+ format = "BMP"
+ # --------------------------------------------------- BMP Compression values
+ COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5}
+ RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
+
+ def _bitmap(self, header=0, offset=0):
+ """ Read relevant info about the BMP """
+ read, seek = self.fp.read, self.fp.seek
+ if header:
+ seek(header)
+ file_info = dict()
+ file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
+ file_info['direction'] = -1
+ # --------------------- If requested, read header at a specific position
+ header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size
+ # --------------------------------------------------- IBM OS/2 Bitmap v1
+ # ------ This format has different offsets because of width/height types
+ if file_info['header_size'] == 12:
+ file_info['width'] = i16(header_data[0:2])
+ file_info['height'] = i16(header_data[2:4])
+ file_info['planes'] = i16(header_data[4:6])
+ file_info['bits'] = i16(header_data[6:8])
+ file_info['compression'] = self.RAW
+ file_info['palette_padding'] = 3
+ # ---------------------------------------------- Windows Bitmap v2 to v5
+ elif file_info['header_size'] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5
+ if file_info['header_size'] >= 40: # v3 and OS/2
+ file_info['y_flip'] = i8(header_data[7]) == 0xff
+ file_info['direction'] = 1 if file_info['y_flip'] else -1
+ file_info['width'] = i32(header_data[0:4])
+ file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8])
+ file_info['planes'] = i16(header_data[8:10])
+ file_info['bits'] = i16(header_data[10:12])
+ file_info['compression'] = i32(header_data[12:16])
+ file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data
+ file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
+ file_info['colors'] = i32(header_data[28:32])
+ file_info['palette_padding'] = 4
+ self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter']))
+ if file_info['compression'] == self.BITFIELDS:
+ if len(header_data) >= 52:
+ for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
+ file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
+ else:
+ for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
+ file_info[mask] = i32(read(4))
+ file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
+ file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
+ else:
+ raise IOError("Unsupported BMP header type (%d)" % file_info['header_size'])
+ # ------------------ Special case : header is reported 40, which
+ # ---------------------- is shorter than real size for bpp >= 16
+ self.size = file_info['width'], file_info['height']
+ # -------- If color count was not found in the header, compute from bits
+ file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits'])
+ # -------------------------------- Check abnormal values for DOS attacks
+ if file_info['width'] * file_info['height'] > 2**31:
+ raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
+ # ----------------------- Check bit depth for unusual unsupported values
+ self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
+ if self.mode is None:
+ raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits'])
+ # ----------------- Process BMP with Bitfields compression (not palette)
+ if file_info['compression'] == self.BITFIELDS:
+ SUPPORTED = {
+ 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
+ 24: [(0xff0000, 0xff00, 0xff)],
+ 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]}
+ MASK_MODES = {
+ (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
+ (24, (0xff0000, 0xff00, 0xff)): "BGR",
+ (16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"}
+ if file_info['bits'] in SUPPORTED:
+ if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
+ raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
+ self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
+ elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]:
+ raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])]
+ else:
+ raise IOError("Unsupported BMP bitfields layout")
+ else:
+ raise IOError("Unsupported BMP bitfields layout")
+ elif file_info['compression'] == self.RAW:
+ if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset
+ raw_mode, self.mode = "BGRA", "RGBA"
+ else:
+ raise IOError("Unsupported BMP compression (%d)" % file_info['compression'])
+ # ---------------- Once the header is processed, process the palette/LUT
+ if self.mode == "P": # Paletted for 1, 4 and 8 bit images
+ # ----------------------------------------------------- 1-bit images
+ if not (0 < file_info['colors'] <= 65536):
+ raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors'])
+ else:
+ padding = file_info['palette_padding']
+ palette = read(padding * file_info['colors'])
+ greyscale = True
+ indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors']))
+ # ------------------ Check if greyscale and ignore palette if so
+ for ind, val in enumerate(indices):
+ rgb = palette[ind*padding:ind*padding + 3]
+ if rgb != o8(val) * 3:
+ greyscale = False
+ # -------- If all colors are grey, white or black, ditch palette
+ if greyscale:
+ self.mode = "1" if file_info['colors'] == 2 else "L"
+ raw_mode = self.mode
+ else:
+ self.mode = "P"
+ self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette)
+
+ # ----------------------------- Finally set the tile data for the plugin
+ self.info['compression'] = file_info['compression']
+ self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(),
+ (raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])
+ )]
+
+ def _open(self):
+ """ Open file, check magic number and read header """
+ # read 14 bytes: magic number, filesize, reserved, header final offset
+ head_data = self.fp.read(14)
+ # choke if the file does not have the required magic bytes
+ if head_data[0:2] != b"BM":
+ raise SyntaxError("Not a BMP file")
+ # read the start position of the BMP image data (u32)
+ offset = i32(head_data[10:14])
+ # load bitmap information (offset=raster info)
+ self._bitmap(offset=offset)
+
+
+# ==============================================================================
+# Image plugin for the DIB format (BMP alias)
+# ==============================================================================
+class DibImageFile(BmpImageFile):
+
+ format = "DIB"
+ format_description = "Windows Bitmap"
+
+ def _open(self):
+ self._bitmap()
+
+#
+# --------------------------------------------------------------------
+# Write BMP file
+
+SAVE = {
+ "1": ("1", 1, 2),
+ "L": ("L", 8, 256),
+ "P": ("P", 8, 256),
+ "RGB": ("BGR", 24, 0),
+ "RGBA": ("BGRA", 32, 0),
+}
+
+
+def _save(im, fp, filename, check=0):
+ try:
+ rawmode, bits, colors = SAVE[im.mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as BMP" % im.mode)
+
+ if check:
+ return check
+
+ info = im.encoderinfo
+
+ dpi = info.get("dpi", (96, 96))
+
+ # 1 meter == 39.3701 inches
+ ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
+
+ stride = ((im.size[0]*bits+7)//8+3) & (~3)
+ header = 40 # or 64 for OS/2 version 2
+ offset = 14 + header + colors * 4
+ image = stride * im.size[1]
+
+ # bitmap header
+ fp.write(b"BM" + # file type (magic)
+ o32(offset+image) + # file size
+ o32(0) + # reserved
+ o32(offset)) # image data offset
+
+ # bitmap info header
+ fp.write(o32(header) + # info header size
+ o32(im.size[0]) + # width
+ o32(im.size[1]) + # height
+ o16(1) + # planes
+ o16(bits) + # depth
+ o32(0) + # compression (0=uncompressed)
+ o32(image) + # size of bitmap
+ o32(ppm[0]) + o32(ppm[1]) + # resolution
+ o32(colors) + # colors used
+ o32(colors)) # colors important
+
+ fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
+
+ if im.mode == "1":
+ for i in (0, 255):
+ fp.write(o8(i) * 4)
+ elif im.mode == "L":
+ for i in range(256):
+ fp.write(o8(i) * 4)
+ elif im.mode == "P":
+ fp.write(im.im.getpalette("RGB", "BGRX"))
+
+ ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0,
+ (rawmode, stride, -1))])
+
+#
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
+Image.register_save(BmpImageFile.format, _save)
+
+Image.register_extension(BmpImageFile.format, ".bmp")
diff --git a/lib/Python/Lib/PIL/BufrStubImagePlugin.py b/lib/Python/Lib/PIL/BufrStubImagePlugin.py
new file mode 100644
index 000000000..45ee54776
--- /dev/null
+++ b/lib/Python/Lib/PIL/BufrStubImagePlugin.py
@@ -0,0 +1,72 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# BUFR stub adapter
+#
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFile
+
+_handler = None
+
+
+##
+# Install application-specific BUFR image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
+
+
+class BufrStubImageFile(ImageFile.StubImageFile):
+
+ format = "BUFR"
+ format_description = "BUFR"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ raise SyntaxError("Not a BUFR file")
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("BUFR save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
+Image.register_save(BufrStubImageFile.format, _save)
+
+Image.register_extension(BufrStubImageFile.format, ".bufr")
diff --git a/lib/Python/Lib/PIL/ContainerIO.py b/lib/Python/Lib/PIL/ContainerIO.py
new file mode 100644
index 000000000..dcedcd6dd
--- /dev/null
+++ b/lib/Python/Lib/PIL/ContainerIO.py
@@ -0,0 +1,117 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# a class to read from a container file
+#
+# History:
+# 1995-06-18 fl Created
+# 1995-09-07 fl Added readline(), readlines()
+#
+# Copyright (c) 1997-2001 by Secret Labs AB
+# Copyright (c) 1995 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# A file object that provides read access to a part of an existing
+# file (for example a TAR file).
+
+
+class ContainerIO:
+
+ ##
+ # Create file object.
+ #
+ # @param file Existing file.
+ # @param offset Start of region, in bytes.
+ # @param length Size of region, in bytes.
+
+ def __init__(self, file, offset, length):
+ self.fh = file
+ self.pos = 0
+ self.offset = offset
+ self.length = length
+ self.fh.seek(offset)
+
+ ##
+ # Always false.
+
+ def isatty(self):
+ return 0
+
+ ##
+ # Move file pointer.
+ #
+ # @param offset Offset in bytes.
+ # @param mode Starting position. Use 0 for beginning of region, 1
+ # for current offset, and 2 for end of region. You cannot move
+ # the pointer outside the defined region.
+
+ def seek(self, offset, mode=0):
+ if mode == 1:
+ self.pos = self.pos + offset
+ elif mode == 2:
+ self.pos = self.length + offset
+ else:
+ self.pos = offset
+ # clamp
+ self.pos = max(0, min(self.pos, self.length))
+ self.fh.seek(self.offset + self.pos)
+
+ ##
+ # Get current file pointer.
+ #
+ # @return Offset from start of region, in bytes.
+
+ def tell(self):
+ return self.pos
+
+ ##
+ # Read data.
+ #
+ # @def read(bytes=0)
+ # @param bytes Number of bytes to read. If omitted or zero,
+ # read until end of region.
+ # @return An 8-bit string.
+
+ def read(self, n=0):
+ if n:
+ n = min(n, self.length - self.pos)
+ else:
+ n = self.length - self.pos
+ if not n: # EOF
+ return ""
+ self.pos = self.pos + n
+ return self.fh.read(n)
+
+ ##
+ # Read a line of text.
+ #
+ # @return An 8-bit string.
+
+ def readline(self):
+ s = ""
+ while True:
+ c = self.read(1)
+ if not c:
+ break
+ s = s + c
+ if c == "\n":
+ break
+ return s
+
+ ##
+ # Read multiple lines of text.
+ #
+ # @return A list of 8-bit strings.
+
+ def readlines(self):
+ l = []
+ while True:
+ s = self.readline()
+ if not s:
+ break
+ l.append(s)
+ return l
diff --git a/lib/Python/Lib/PIL/CurImagePlugin.py b/lib/Python/Lib/PIL/CurImagePlugin.py
new file mode 100644
index 000000000..0178957ee
--- /dev/null
+++ b/lib/Python/Lib/PIL/CurImagePlugin.py
@@ -0,0 +1,87 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Windows Cursor support for PIL
+#
+# notes:
+# uses BmpImagePlugin.py to read the bitmap data.
+#
+# history:
+# 96-05-27 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+from PIL import Image, BmpImagePlugin, _binary
+
+
+#
+# --------------------------------------------------------------------
+
+i8 = _binary.i8
+i16 = _binary.i16le
+i32 = _binary.i32le
+
+
+def _accept(prefix):
+ return prefix[:4] == b"\0\0\2\0"
+
+
+##
+# Image plugin for Windows Cursor files.
+
+class CurImageFile(BmpImagePlugin.BmpImageFile):
+
+ format = "CUR"
+ format_description = "Windows Cursor"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ # check magic
+ s = self.fp.read(6)
+ if not _accept(s):
+ raise SyntaxError("not a CUR file")
+
+ # pick the largest cursor in the file
+ m = b""
+ for i in range(i16(s[4:])):
+ s = self.fp.read(16)
+ if not m:
+ m = s
+ elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
+ m = s
+ # print "width", i8(s[0])
+ # print "height", i8(s[1])
+ # print "colors", i8(s[2])
+ # print "reserved", i8(s[3])
+ # print "hotspot x", i16(s[4:])
+ # print "hotspot y", i16(s[6:])
+ # print "bytes", i32(s[8:])
+ # print "offset", i32(s[12:])
+
+ # load as bitmap
+ self._bitmap(i32(m[12:]) + offset)
+
+ # patch up the bitmap height
+ self.size = self.size[0], self.size[1]//2
+ d, e, o, a = self.tile[0]
+ self.tile[0] = d, (0, 0)+self.size, o, a
+
+ return
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("CUR", CurImageFile, _accept)
+
+Image.register_extension("CUR", ".cur")
diff --git a/lib/Python/Lib/PIL/DcxImagePlugin.py b/lib/Python/Lib/PIL/DcxImagePlugin.py
new file mode 100644
index 000000000..0940b3935
--- /dev/null
+++ b/lib/Python/Lib/PIL/DcxImagePlugin.py
@@ -0,0 +1,79 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# DCX file handling
+#
+# DCX is a container file format defined by Intel, commonly used
+# for fax applications. Each DCX file consists of a directory
+# (a list of file offsets) followed by a set of (usually 1-bit)
+# PCX files.
+#
+# History:
+# 1995-09-09 fl Created
+# 1996-03-20 fl Properly derived from PcxImageFile.
+# 1998-07-15 fl Renamed offset attribute to avoid name clash
+# 2002-07-30 fl Fixed file handling
+#
+# Copyright (c) 1997-98 by Secret Labs AB.
+# Copyright (c) 1995-96 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.2"
+
+from PIL import Image, _binary
+
+from PIL.PcxImagePlugin import PcxImageFile
+
+MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
+
+i32 = _binary.i32le
+
+
+def _accept(prefix):
+ return i32(prefix) == MAGIC
+
+
+##
+# Image plugin for the Intel DCX format.
+
+class DcxImageFile(PcxImageFile):
+
+ format = "DCX"
+ format_description = "Intel DCX"
+
+ def _open(self):
+
+ # Header
+ s = self.fp.read(4)
+ if i32(s) != MAGIC:
+ raise SyntaxError("not a DCX file")
+
+ # Component directory
+ self._offset = []
+ for i in range(1024):
+ offset = i32(self.fp.read(4))
+ if not offset:
+ break
+ self._offset.append(offset)
+
+ self.__fp = self.fp
+ self.seek(0)
+
+ def seek(self, frame):
+ if frame >= len(self._offset):
+ raise EOFError("attempt to seek outside DCX directory")
+ self.frame = frame
+ self.fp = self.__fp
+ self.fp.seek(self._offset[frame])
+ PcxImageFile._open(self)
+
+ def tell(self):
+ return self.frame
+
+
+Image.register_open("DCX", DcxImageFile, _accept)
+
+Image.register_extension("DCX", ".dcx")
diff --git a/lib/Python/Lib/PIL/EpsImagePlugin.py b/lib/Python/Lib/PIL/EpsImagePlugin.py
new file mode 100644
index 000000000..83024b63f
--- /dev/null
+++ b/lib/Python/Lib/PIL/EpsImagePlugin.py
@@ -0,0 +1,424 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# EPS file handling
+#
+# History:
+# 1995-09-01 fl Created (0.1)
+# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
+# 1996-08-22 fl Don't choke on floating point BoundingBox values
+# 1996-08-23 fl Handle files from Macintosh (0.3)
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
+# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
+# 2014-05-07 e Handling of EPS with binary preview and fixed resolution
+# resizing
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.5"
+
+import re
+import io
+from PIL import Image, ImageFile, _binary
+
+#
+# --------------------------------------------------------------------
+
+i32 = _binary.i32le
+o32 = _binary.o32le
+
+split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
+field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
+
+gs_windows_binary = None
+import sys
+if sys.platform.startswith('win'):
+ import shutil
+ if hasattr(shutil, 'which'):
+ which = shutil.which
+ else:
+ # Python < 3.3
+ import distutils.spawn
+ which = distutils.spawn.find_executable
+ for binary in ('gswin32c', 'gswin64c', 'gs'):
+ if which(binary) is not None:
+ gs_windows_binary = binary
+ break
+ else:
+ gs_windows_binary = False
+
+
+def has_ghostscript():
+ if gs_windows_binary:
+ return True
+ if not sys.platform.startswith('win'):
+ import subprocess
+ try:
+ gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
+ gs.stdout.read()
+ return True
+ except OSError:
+ # no ghostscript
+ pass
+ return False
+
+
+def Ghostscript(tile, size, fp, scale=1):
+ """Render an image using Ghostscript"""
+
+ # Unpack decoder tile
+ decoder, tile, offset, data = tile[0]
+ length, bbox = data
+
+ # Hack to support hi-res rendering
+ scale = int(scale) or 1
+ # orig_size = size
+ # orig_bbox = bbox
+ size = (size[0] * scale, size[1] * scale)
+ # resolution is dependent on bbox and size
+ res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
+ float((72.0 * size[1]) / (bbox[3]-bbox[1])))
+ # print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
+
+ import os
+ import subprocess
+ import tempfile
+
+ out_fd, outfile = tempfile.mkstemp()
+ os.close(out_fd)
+
+ infile_temp = None
+ if hasattr(fp, 'name') and os.path.exists(fp.name):
+ infile = fp.name
+ else:
+ in_fd, infile_temp = tempfile.mkstemp()
+ os.close(in_fd)
+ infile = infile_temp
+
+ # ignore length and offset!
+ # ghostscript can read it
+ # copy whole file to read in ghostscript
+ with open(infile_temp, 'wb') as f:
+ # fetch length of fp
+ fp.seek(0, 2)
+ fsize = fp.tell()
+ # ensure start position
+ # go back
+ fp.seek(0)
+ lengthfile = fsize
+ while lengthfile > 0:
+ s = fp.read(min(lengthfile, 100*1024))
+ if not s:
+ break
+ lengthfile -= len(s)
+ f.write(s)
+
+ # Build ghostscript command
+ command = ["gs",
+ "-q", # quiet mode
+ "-g%dx%d" % size, # set output geometry (pixels)
+ "-r%fx%f" % res, # set input DPI (dots per inch)
+ "-dNOPAUSE -dSAFER", # don't pause between pages,
+ # safe mode
+ "-sDEVICE=ppmraw", # ppm driver
+ "-sOutputFile=%s" % outfile, # output file
+ "-c", "%d %d translate" % (-bbox[0], -bbox[1]),
+ # adjust for image origin
+ "-f", infile, # input file
+ ]
+
+ if gs_windows_binary is not None:
+ if not gs_windows_binary:
+ raise WindowsError('Unable to locate Ghostscript on paths')
+ command[0] = gs_windows_binary
+
+ # push data through ghostscript
+ try:
+ gs = subprocess.Popen(command, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ gs.stdin.close()
+ status = gs.wait()
+ if status:
+ raise IOError("gs failed (status %d)" % status)
+ im = Image.core.open_ppm(outfile)
+ finally:
+ try:
+ os.unlink(outfile)
+ if infile_temp:
+ os.unlink(infile_temp)
+ except:
+ pass
+
+ return im
+
+
+class PSFile:
+ """
+ Wrapper for bytesio object that treats either CR or LF as end of line.
+ """
+ def __init__(self, fp):
+ self.fp = fp
+ self.char = None
+
+ def seek(self, offset, whence=0):
+ self.char = None
+ self.fp.seek(offset, whence)
+
+ def readline(self):
+ s = self.char or b""
+ self.char = None
+
+ c = self.fp.read(1)
+ while c not in b"\r\n":
+ s = s + c
+ c = self.fp.read(1)
+
+ self.char = self.fp.read(1)
+ # line endings can be 1 or 2 of \r \n, in either order
+ if self.char in b"\r\n":
+ self.char = None
+
+ return s.decode('latin-1')
+
+
+def _accept(prefix):
+ return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
+
+##
+# Image plugin for Encapsulated Postscript. This plugin supports only
+# a few variants of this format.
+
+
+class EpsImageFile(ImageFile.ImageFile):
+ """EPS File Parser for the Python Imaging Library"""
+
+ format = "EPS"
+ format_description = "Encapsulated Postscript"
+
+ mode_map = {1: "L", 2: "LAB", 3: "RGB"}
+
+ def _open(self):
+ (length, offset) = self._find_offset(self.fp)
+
+ # Rewrap the open file pointer in something that will
+ # convert line endings and decode to latin-1.
+ try:
+ if bytes is str:
+ # Python2, no encoding conversion necessary
+ fp = open(self.fp.name, "Ur")
+ else:
+ # Python3, can use bare open command.
+ fp = open(self.fp.name, "Ur", encoding='latin-1')
+ except:
+ # Expect this for bytesio/stringio
+ fp = PSFile(self.fp)
+
+ # go to offset - start of "%!PS"
+ fp.seek(offset)
+
+ box = None
+
+ self.mode = "RGB"
+ self.size = 1, 1 # FIXME: huh?
+
+ #
+ # Load EPS header
+
+ s = fp.readline().strip('\r\n')
+
+ while s:
+ if len(s) > 255:
+ raise SyntaxError("not an EPS file")
+
+ try:
+ m = split.match(s)
+ except re.error as v:
+ raise SyntaxError("not an EPS file")
+
+ if m:
+ k, v = m.group(1, 2)
+ self.info[k] = v
+ if k == "BoundingBox":
+ try:
+ # Note: The DSC spec says that BoundingBox
+ # fields should be integers, but some drivers
+ # put floating point values there anyway.
+ box = [int(float(s)) for s in v.split()]
+ self.size = box[2] - box[0], box[3] - box[1]
+ self.tile = [("eps", (0, 0) + self.size, offset,
+ (length, box))]
+ except:
+ pass
+
+ else:
+ m = field.match(s)
+ if m:
+ k = m.group(1)
+
+ if k == "EndComments":
+ break
+ if k[:8] == "PS-Adobe":
+ self.info[k[:8]] = k[9:]
+ else:
+ self.info[k] = ""
+ elif s[0] == '%':
+ # handle non-DSC Postscript comments that some
+ # tools mistakenly put in the Comments section
+ pass
+ else:
+ raise IOError("bad EPS header")
+
+ s = fp.readline().strip('\r\n')
+
+ if s[:1] != "%":
+ break
+
+ #
+ # Scan for an "ImageData" descriptor
+
+ while s[:1] == "%":
+
+ if len(s) > 255:
+ raise SyntaxError("not an EPS file")
+
+ if s[:11] == "%ImageData:":
+ # Encoded bitmapped image.
+ [x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
+
+ if int(bi) != 8:
+ break
+ try:
+ self.mode = self.mode_map[int(mo)]
+ except:
+ break
+
+ self.size = int(x), int(y)
+ return
+
+ s = fp.readline().strip('\r\n')
+ if not s:
+ break
+
+ if not box:
+ raise IOError("cannot determine EPS bounding box")
+
+ def _find_offset(self, fp):
+
+ s = fp.read(160)
+
+ if s[:4] == b"%!PS":
+ # for HEAD without binary preview
+ fp.seek(0, 2)
+ length = fp.tell()
+ offset = 0
+ elif i32(s[0:4]) == 0xC6D3D0C5:
+ # FIX for: Some EPS file not handled correctly / issue #302
+ # EPS can contain binary data
+ # or start directly with latin coding
+ # more info see:
+ # http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
+ offset = i32(s[4:8])
+ length = i32(s[8:12])
+ else:
+ raise SyntaxError("not an EPS file")
+
+ return (length, offset)
+
+ def load(self, scale=1):
+ # Load EPS via Ghostscript
+ if not self.tile:
+ return
+ self.im = Ghostscript(self.tile, self.size, self.fp, scale)
+ self.mode = self.im.mode
+ self.size = self.im.size
+ self.tile = []
+
+ def load_seek(self, *args, **kwargs):
+ # we can't incrementally load, so force ImageFile.parser to
+ # use our custom load method by defining this method.
+ pass
+
+
+#
+# --------------------------------------------------------------------
+
+def _save(im, fp, filename, eps=1):
+ """EPS Writer for the Python Imaging Library."""
+
+ #
+ # make sure image data is available
+ im.load()
+
+ #
+ # determine postscript image mode
+ if im.mode == "L":
+ operator = (8, 1, "image")
+ elif im.mode == "RGB":
+ operator = (8, 3, "false 3 colorimage")
+ elif im.mode == "CMYK":
+ operator = (8, 4, "false 4 colorimage")
+ else:
+ raise ValueError("image mode is not supported")
+
+ class NoCloseStream:
+ def __init__(self, fp):
+ self.fp = fp
+
+ def __getattr__(self, name):
+ return getattr(self.fp, name)
+
+ def close(self):
+ pass
+
+ base_fp = fp
+ fp = NoCloseStream(fp)
+ if sys.version_info[0] > 2:
+ fp = io.TextIOWrapper(fp, encoding='latin-1')
+
+ if eps:
+ #
+ # write EPS header
+ fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
+ fp.write("%%Creator: PIL 0.1 EpsEncode\n")
+ # fp.write("%%CreationDate: %s"...)
+ fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
+ fp.write("%%Pages: 1\n")
+ fp.write("%%EndComments\n")
+ fp.write("%%Page: 1 1\n")
+ fp.write("%%ImageData: %d %d " % im.size)
+ fp.write("%d %d 0 1 1 \"%s\"\n" % operator)
+
+ #
+ # image header
+ fp.write("gsave\n")
+ fp.write("10 dict begin\n")
+ fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
+ fp.write("%d %d scale\n" % im.size)
+ fp.write("%d %d 8\n" % im.size) # <= bits
+ fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
+ fp.write("{ currentfile buf readhexstring pop } bind\n")
+ fp.write(operator[2] + "\n")
+ fp.flush()
+
+ ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
+
+ fp.write("\n%%%%EndBinary\n")
+ fp.write("grestore end\n")
+ fp.flush()
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
+
+Image.register_save(EpsImageFile.format, _save)
+
+Image.register_extension(EpsImageFile.format, ".ps")
+Image.register_extension(EpsImageFile.format, ".eps")
+
+Image.register_mime(EpsImageFile.format, "application/postscript")
diff --git a/lib/Python/Lib/PIL/ExifTags.py b/lib/Python/Lib/PIL/ExifTags.py
new file mode 100644
index 000000000..52e145f62
--- /dev/null
+++ b/lib/Python/Lib/PIL/ExifTags.py
@@ -0,0 +1,193 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# EXIF tags
+#
+# Copyright (c) 2003 by Secret Labs AB
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# This module provides constants and clear-text names for various
+# well-known EXIF tags.
+##
+
+##
+# Maps EXIF tags to tag names.
+
+TAGS = {
+
+ # possibly incomplete
+ 0x00fe: "NewSubfileType",
+ 0x00ff: "SubfileType",
+ 0x0100: "ImageWidth",
+ 0x0101: "ImageLength",
+ 0x0102: "BitsPerSample",
+ 0x0103: "Compression",
+ 0x0106: "PhotometricInterpretation",
+ 0x0107: "Threshholding",
+ 0x0108: "CellWidth",
+ 0x0109: "CellLenght",
+ 0x010a: "FillOrder",
+ 0x010d: "DocumentName",
+ 0x011d: "PageName",
+ 0x010e: "ImageDescription",
+ 0x010f: "Make",
+ 0x0110: "Model",
+ 0x0111: "StripOffsets",
+ 0x0112: "Orientation",
+ 0x0115: "SamplesPerPixel",
+ 0x0116: "RowsPerStrip",
+ 0x0117: "StripByteConunts",
+ 0x0118: "MinSampleValue",
+ 0x0119: "MaxSampleValue",
+ 0x011a: "XResolution",
+ 0x011b: "YResolution",
+ 0x011c: "PlanarConfiguration",
+ 0x0120: "FreeOffsets",
+ 0x0121: "FreeByteCounts",
+ 0x0122: "GrayResponseUnit",
+ 0x0123: "GrayResponseCurve",
+ 0x0128: "ResolutionUnit",
+ 0x012d: "TransferFunction",
+ 0x0131: "Software",
+ 0x0132: "DateTime",
+ 0x013b: "Artist",
+ 0x013c: "HostComputer",
+ 0x013e: "WhitePoint",
+ 0x013f: "PrimaryChromaticities",
+ 0x0140: "ColorMap",
+ 0x0152: "ExtraSamples",
+ 0x0201: "JpegIFOffset",
+ 0x0202: "JpegIFByteCount",
+ 0x0211: "YCbCrCoefficients",
+ 0x0212: "YCbCrSubSampling",
+ 0x0213: "YCbCrPositioning",
+ 0x0214: "ReferenceBlackWhite",
+ 0x1000: "RelatedImageFileFormat",
+ 0x1001: "RelatedImageWidth",
+ 0x1002: "RelatedImageLength",
+ 0x828d: "CFARepeatPatternDim",
+ 0x828e: "CFAPattern",
+ 0x828f: "BatteryLevel",
+ 0x8298: "Copyright",
+ 0x829a: "ExposureTime",
+ 0x829d: "FNumber",
+ 0x8769: "ExifOffset",
+ 0x8773: "InterColorProfile",
+ 0x8822: "ExposureProgram",
+ 0x8824: "SpectralSensitivity",
+ 0x8825: "GPSInfo",
+ 0x8827: "ISOSpeedRatings",
+ 0x8828: "OECF",
+ 0x8829: "Interlace",
+ 0x882a: "TimeZoneOffset",
+ 0x882b: "SelfTimerMode",
+ 0x9000: "ExifVersion",
+ 0x9003: "DateTimeOriginal",
+ 0x9004: "DateTimeDigitized",
+ 0x9101: "ComponentsConfiguration",
+ 0x9102: "CompressedBitsPerPixel",
+ 0x9201: "ShutterSpeedValue",
+ 0x9202: "ApertureValue",
+ 0x9203: "BrightnessValue",
+ 0x9204: "ExposureBiasValue",
+ 0x9205: "MaxApertureValue",
+ 0x9206: "SubjectDistance",
+ 0x9207: "MeteringMode",
+ 0x9208: "LightSource",
+ 0x9209: "Flash",
+ 0x920a: "FocalLength",
+ 0x920b: "FlashEnergy",
+ 0x920c: "SpatialFrequencyResponse",
+ 0x920d: "Noise",
+ 0x9211: "ImageNumber",
+ 0x9212: "SecurityClassification",
+ 0x9213: "ImageHistory",
+ 0x9214: "SubjectLocation",
+ 0x9215: "ExposureIndex",
+ 0x9216: "TIFF/EPStandardID",
+ 0x927c: "MakerNote",
+ 0x9286: "UserComment",
+ 0x9290: "SubsecTime",
+ 0x9291: "SubsecTimeOriginal",
+ 0x9292: "SubsecTimeDigitized",
+ 0xa000: "FlashPixVersion",
+ 0xa001: "ColorSpace",
+ 0xa002: "ExifImageWidth",
+ 0xa003: "ExifImageHeight",
+ 0xa004: "RelatedSoundFile",
+ 0xa005: "ExifInteroperabilityOffset",
+ 0xa20b: "FlashEnergy",
+ 0xa20c: "SpatialFrequencyResponse",
+ 0xa20e: "FocalPlaneXResolution",
+ 0xa20f: "FocalPlaneYResolution",
+ 0xa210: "FocalPlaneResolutionUnit",
+ 0xa214: "SubjectLocation",
+ 0xa215: "ExposureIndex",
+ 0xa217: "SensingMethod",
+ 0xa300: "FileSource",
+ 0xa301: "SceneType",
+ 0xa302: "CFAPattern",
+ 0xa401: "CustomRendered",
+ 0xa402: "ExposureMode",
+ 0xa403: "WhiteBalance",
+ 0xa404: "DigitalZoomRatio",
+ 0xa405: "FocalLengthIn35mmFilm",
+ 0xa406: "SceneCaptureType",
+ 0xa407: "GainControl",
+ 0xa408: "Contrast",
+ 0xa409: "Saturation",
+ 0xa40a: "Sharpness",
+ 0xa40b: "DeviceSettingDescription",
+ 0xa40c: "SubjectDistanceRange",
+ 0xa420: "ImageUniqueID",
+ 0xa430: "CameraOwnerName",
+ 0xa431: "BodySerialNumber",
+ 0xa432: "LensSpecification",
+ 0xa433: "LensMake",
+ 0xa434: "LensModel",
+ 0xa435: "LensSerialNumber",
+ 0xa500: "Gamma",
+
+}
+
+##
+# Maps EXIF GPS tags to tag names.
+
+GPSTAGS = {
+ 0: "GPSVersionID",
+ 1: "GPSLatitudeRef",
+ 2: "GPSLatitude",
+ 3: "GPSLongitudeRef",
+ 4: "GPSLongitude",
+ 5: "GPSAltitudeRef",
+ 6: "GPSAltitude",
+ 7: "GPSTimeStamp",
+ 8: "GPSSatellites",
+ 9: "GPSStatus",
+ 10: "GPSMeasureMode",
+ 11: "GPSDOP",
+ 12: "GPSSpeedRef",
+ 13: "GPSSpeed",
+ 14: "GPSTrackRef",
+ 15: "GPSTrack",
+ 16: "GPSImgDirectionRef",
+ 17: "GPSImgDirection",
+ 18: "GPSMapDatum",
+ 19: "GPSDestLatitudeRef",
+ 20: "GPSDestLatitude",
+ 21: "GPSDestLongitudeRef",
+ 22: "GPSDestLongitude",
+ 23: "GPSDestBearingRef",
+ 24: "GPSDestBearing",
+ 25: "GPSDestDistanceRef",
+ 26: "GPSDestDistance",
+ 27: "GPSProcessingMethod",
+ 28: "GPSAreaInformation",
+ 29: "GPSDateStamp",
+ 30: "GPSDifferential",
+ 31: "GPSHPositioningError",
+}
diff --git a/lib/Python/Lib/PIL/FitsStubImagePlugin.py b/lib/Python/Lib/PIL/FitsStubImagePlugin.py
new file mode 100644
index 000000000..0b851ae59
--- /dev/null
+++ b/lib/Python/Lib/PIL/FitsStubImagePlugin.py
@@ -0,0 +1,73 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# FITS stub adapter
+#
+# Copyright (c) 1998-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFile
+
+_handler = None
+
+##
+# Install application-specific FITS image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[:6] == b"SIMPLE"
+
+class FITSStubImageFile(ImageFile.StubImageFile):
+
+ format = "FITS"
+ format_description = "FITS"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(6)):
+ raise SyntaxError("Not a FITS file")
+
+ # FIXME: add more sanity checks here; mandatory header items
+ # include SIMPLE, BITPIX, NAXIS, etc.
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("FITS save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(FITSStubImageFile.format, FITSStubImageFile, _accept)
+Image.register_save(FITSStubImageFile.format, _save)
+
+Image.register_extension(FITSStubImageFile.format, ".fit")
+Image.register_extension(FITSStubImageFile.format, ".fits")
diff --git a/lib/Python/Lib/PIL/FliImagePlugin.py b/lib/Python/Lib/PIL/FliImagePlugin.py
new file mode 100644
index 000000000..4ecaccdc4
--- /dev/null
+++ b/lib/Python/Lib/PIL/FliImagePlugin.py
@@ -0,0 +1,143 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# FLI/FLC file handling.
+#
+# History:
+# 95-09-01 fl Created
+# 97-01-03 fl Fixed parser, setup decoder tile
+# 98-07-15 fl Renamed offset attribute to avoid name clash
+#
+# Copyright (c) Secret Labs AB 1997-98.
+# Copyright (c) Fredrik Lundh 1995-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+
+i8 = _binary.i8
+i16 = _binary.i16le
+i32 = _binary.i32le
+o8 = _binary.o8
+
+
+#
+# decoder
+
+def _accept(prefix):
+ return i16(prefix[4:6]) in [0xAF11, 0xAF12]
+
+
+##
+# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
+# method to load individual frames.
+
+class FliImageFile(ImageFile.ImageFile):
+
+ format = "FLI"
+ format_description = "Autodesk FLI/FLC Animation"
+
+ def _open(self):
+
+ # HEAD
+ s = self.fp.read(128)
+ magic = i16(s[4:6])
+ if not (magic in [0xAF11, 0xAF12] and
+ i16(s[14:16]) in [0, 3] and # flags
+ s[20:22] == b"\x00\x00"): # reserved
+ raise SyntaxError("not an FLI/FLC file")
+
+ # image characteristics
+ self.mode = "P"
+ self.size = i16(s[8:10]), i16(s[10:12])
+
+ # animation speed
+ duration = i32(s[16:20])
+ if magic == 0xAF11:
+ duration = (duration * 1000) / 70
+ self.info["duration"] = duration
+
+ # look for palette
+ palette = [(a, a, a) for a in range(256)]
+
+ s = self.fp.read(16)
+
+ self.__offset = 128
+
+ if i16(s[4:6]) == 0xF100:
+ # prefix chunk; ignore it
+ self.__offset = self.__offset + i32(s)
+ s = self.fp.read(16)
+
+ if i16(s[4:6]) == 0xF1FA:
+ # look for palette chunk
+ s = self.fp.read(6)
+ if i16(s[4:6]) == 11:
+ self._palette(palette, 2)
+ elif i16(s[4:6]) == 4:
+ self._palette(palette, 0)
+
+ palette = [o8(r)+o8(g)+o8(b) for (r, g, b) in palette]
+ self.palette = ImagePalette.raw("RGB", b"".join(palette))
+
+ # set things up to decode first frame
+ self.frame = -1
+ self.__fp = self.fp
+
+ self.seek(0)
+
+ def _palette(self, palette, shift):
+ # load palette
+
+ i = 0
+ for e in range(i16(self.fp.read(2))):
+ s = self.fp.read(2)
+ i = i + i8(s[0])
+ n = i8(s[1])
+ if n == 0:
+ n = 256
+ s = self.fp.read(n * 3)
+ for n in range(0, len(s), 3):
+ r = i8(s[n]) << shift
+ g = i8(s[n+1]) << shift
+ b = i8(s[n+2]) << shift
+ palette[i] = (r, g, b)
+ i += 1
+
+ def seek(self, frame):
+
+ if frame != self.frame + 1:
+ raise ValueError("cannot seek to frame %d" % frame)
+ self.frame = frame
+
+ # move to next frame
+ self.fp = self.__fp
+ self.fp.seek(self.__offset)
+
+ s = self.fp.read(4)
+ if not s:
+ raise EOFError
+
+ framesize = i32(s)
+
+ self.decodermaxblock = framesize
+ self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
+
+ self.__offset = self.__offset + framesize
+
+ def tell(self):
+
+ return self.frame
+
+#
+# registry
+
+Image.register_open("FLI", FliImageFile, _accept)
+
+Image.register_extension("FLI", ".fli")
+Image.register_extension("FLI", ".flc")
diff --git a/lib/Python/Lib/PIL/FontFile.py b/lib/Python/Lib/PIL/FontFile.py
new file mode 100644
index 000000000..26ddff0ac
--- /dev/null
+++ b/lib/Python/Lib/PIL/FontFile.py
@@ -0,0 +1,119 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# base class for raster font file parsers
+#
+# history:
+# 1997-06-05 fl created
+# 1997-08-19 fl restrict image width
+#
+# Copyright (c) 1997-1998 by Secret Labs AB
+# Copyright (c) 1997-1998 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import os
+from PIL import Image, _binary
+
+try:
+ import zlib
+except ImportError:
+ zlib = None
+
+WIDTH = 800
+
+
+def puti16(fp, values):
+ # write network order (big-endian) 16-bit sequence
+ for v in values:
+ if v < 0:
+ v += 65536
+ fp.write(_binary.o16be(v))
+
+
+##
+# Base class for raster font file handlers.
+
+class FontFile:
+
+ bitmap = None
+
+ def __init__(self):
+
+ self.info = {}
+ self.glyph = [None] * 256
+
+ def __getitem__(self, ix):
+ return self.glyph[ix]
+
+ def compile(self):
+ "Create metrics and bitmap"
+
+ if self.bitmap:
+ return
+
+ # create bitmap large enough to hold all data
+ h = w = maxwidth = 0
+ lines = 1
+ for glyph in self:
+ if glyph:
+ d, dst, src, im = glyph
+ h = max(h, src[3] - src[1])
+ w = w + (src[2] - src[0])
+ if w > WIDTH:
+ lines += 1
+ w = (src[2] - src[0])
+ maxwidth = max(maxwidth, w)
+
+ xsize = maxwidth
+ ysize = lines * h
+
+ if xsize == 0 and ysize == 0:
+ return ""
+
+ self.ysize = h
+
+ # paste glyphs into bitmap
+ self.bitmap = Image.new("1", (xsize, ysize))
+ self.metrics = [None] * 256
+ x = y = 0
+ for i in range(256):
+ glyph = self[i]
+ if glyph:
+ d, dst, src, im = glyph
+ xx, yy = src[2] - src[0], src[3] - src[1]
+ x0, y0 = x, y
+ x = x + xx
+ if x > WIDTH:
+ x, y = 0, y + h
+ x0, y0 = x, y
+ x = xx
+ s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
+ self.bitmap.paste(im.crop(src), s)
+ # print chr(i), dst, s
+ self.metrics[i] = d, dst, s
+
+ def save(self, filename):
+ "Save font"
+
+ self.compile()
+
+ # font data
+ self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
+
+ # font metrics
+ fp = open(os.path.splitext(filename)[0] + ".pil", "wb")
+ fp.write(b"PILfont\n")
+ fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!!
+ fp.write(b"DATA\n")
+ for id in range(256):
+ m = self.metrics[id]
+ if not m:
+ puti16(fp, [0] * 10)
+ else:
+ puti16(fp, m[0] + m[1] + m[2])
+ fp.close()
+
+# End of file
diff --git a/lib/Python/Lib/PIL/FpxImagePlugin.py b/lib/Python/Lib/PIL/FpxImagePlugin.py
new file mode 100644
index 000000000..ed0c20c4e
--- /dev/null
+++ b/lib/Python/Lib/PIL/FpxImagePlugin.py
@@ -0,0 +1,227 @@
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library.
+# $Id$
+#
+# FlashPix support for PIL
+#
+# History:
+# 97-01-25 fl Created (reads uncompressed RGB images only)
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+
+from PIL import Image, ImageFile
+from PIL.OleFileIO import i8, i32, MAGIC, OleFileIO
+
+
+# we map from colour field tuples to (mode, rawmode) descriptors
+MODES = {
+ # opacity
+ (0x00007ffe): ("A", "L"),
+ # monochrome
+ (0x00010000,): ("L", "L"),
+ (0x00018000, 0x00017ffe): ("RGBA", "LA"),
+ # photo YCC
+ (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
+ (0x00028000, 0x00028001, 0x00028002, 0x00027ffe): ("RGBA", "YCCA;P"),
+ # standard RGB (NIFRGB)
+ (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
+ (0x00038000, 0x00038001, 0x00038002, 0x00037ffe): ("RGBA", "RGBA"),
+}
+
+
+#
+# --------------------------------------------------------------------
+
+def _accept(prefix):
+ return prefix[:8] == MAGIC
+
+
+##
+# Image plugin for the FlashPix images.
+
+class FpxImageFile(ImageFile.ImageFile):
+
+ format = "FPX"
+ format_description = "FlashPix"
+
+ def _open(self):
+ #
+ # read the OLE directory and see if this is a likely
+ # to be a FlashPix file
+
+ try:
+ self.ole = OleFileIO(self.fp)
+ except IOError:
+ raise SyntaxError("not an FPX file; invalid OLE file")
+
+ if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
+ raise SyntaxError("not an FPX file; bad root CLSID")
+
+ self._open_index(1)
+
+ def _open_index(self, index=1):
+ #
+ # get the Image Contents Property Set
+
+ prop = self.ole.getproperties([
+ "Data Object Store %06d" % index,
+ "\005Image Contents"
+ ])
+
+ # size (highest resolution)
+
+ self.size = prop[0x1000002], prop[0x1000003]
+
+ size = max(self.size)
+ i = 1
+ while size > 64:
+ size = size / 2
+ i += 1
+ self.maxid = i - 1
+
+ # mode. instead of using a single field for this, flashpix
+ # requires you to specify the mode for each channel in each
+ # resolution subimage, and leaves it to the decoder to make
+ # sure that they all match. for now, we'll cheat and assume
+ # that this is always the case.
+
+ id = self.maxid << 16
+
+ s = prop[0x2000002 | id]
+
+ colors = []
+ for i in range(i32(s, 4)):
+ # note: for now, we ignore the "uncalibrated" flag
+ colors.append(i32(s, 8+i*4) & 0x7fffffff)
+
+ self.mode, self.rawmode = MODES[tuple(colors)]
+
+ # load JPEG tables, if any
+ self.jpeg = {}
+ for i in range(256):
+ id = 0x3000001 | (i << 16)
+ if id in prop:
+ self.jpeg[i] = prop[id]
+
+ # print len(self.jpeg), "tables loaded"
+
+ self._open_subimage(1, self.maxid)
+
+ def _open_subimage(self, index=1, subimage=0):
+ #
+ # setup tile descriptors for a given subimage
+
+ stream = [
+ "Data Object Store %06d" % index,
+ "Resolution %04d" % subimage,
+ "Subimage 0000 Header"
+ ]
+
+ fp = self.ole.openstream(stream)
+
+ # skip prefix
+ fp.read(28)
+
+ # header stream
+ s = fp.read(36)
+
+ size = i32(s, 4), i32(s, 8)
+ #tilecount = i32(s, 12)
+ tilesize = i32(s, 16), i32(s, 20)
+ #channels = i32(s, 24)
+ offset = i32(s, 28)
+ length = i32(s, 32)
+
+ # print size, self.mode, self.rawmode
+
+ if size != self.size:
+ raise IOError("subimage mismatch")
+
+ # get tile descriptors
+ fp.seek(28 + offset)
+ s = fp.read(i32(s, 12) * length)
+
+ x = y = 0
+ xsize, ysize = size
+ xtile, ytile = tilesize
+ self.tile = []
+
+ for i in range(0, len(s), length):
+
+ compression = i32(s, i+8)
+
+ if compression == 0:
+ self.tile.append(("raw", (x, y, x+xtile, y+ytile),
+ i32(s, i) + 28, (self.rawmode)))
+
+ elif compression == 1:
+
+ # FIXME: the fill decoder is not implemented
+ self.tile.append(("fill", (x, y, x+xtile, y+ytile),
+ i32(s, i) + 28, (self.rawmode, s[12:16])))
+
+ elif compression == 2:
+
+ internal_color_conversion = i8(s[14])
+ jpeg_tables = i8(s[15])
+ rawmode = self.rawmode
+
+ if internal_color_conversion:
+ # The image is stored as usual (usually YCbCr).
+ if rawmode == "RGBA":
+ # For "RGBA", data is stored as YCbCrA based on
+ # negative RGB. The following trick works around
+ # this problem :
+ jpegmode, rawmode = "YCbCrK", "CMYK"
+ else:
+ jpegmode = None # let the decoder decide
+
+ else:
+ # The image is stored as defined by rawmode
+ jpegmode = rawmode
+
+ self.tile.append(("jpeg", (x, y, x+xtile, y+ytile),
+ i32(s, i) + 28, (rawmode, jpegmode)))
+
+ # FIXME: jpeg tables are tile dependent; the prefix
+ # data must be placed in the tile descriptor itself!
+
+ if jpeg_tables:
+ self.tile_prefix = self.jpeg[jpeg_tables]
+
+ else:
+ raise IOError("unknown/invalid compression")
+
+ x = x + xtile
+ if x >= xsize:
+ x, y = 0, y + ytile
+ if y >= ysize:
+ break # isn't really required
+
+ self.stream = stream
+ self.fp = None
+
+ def load(self):
+
+ if not self.fp:
+ self.fp = self.ole.openstream(self.stream[:2] +
+ ["Subimage 0000 Data"])
+
+ ImageFile.ImageFile.load(self)
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("FPX", FpxImageFile, _accept)
+
+Image.register_extension("FPX", ".fpx")
diff --git a/lib/Python/Lib/PIL/GbrImagePlugin.py b/lib/Python/Lib/PIL/GbrImagePlugin.py
new file mode 100644
index 000000000..0e686326c
--- /dev/null
+++ b/lib/Python/Lib/PIL/GbrImagePlugin.py
@@ -0,0 +1,71 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# load a GIMP brush file
+#
+# History:
+# 96-03-14 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFile, _binary
+
+i32 = _binary.i32be
+
+
+def _accept(prefix):
+ return i32(prefix) >= 20 and i32(prefix[4:8]) == 1
+
+
+##
+# Image plugin for the GIMP brush format.
+
+class GbrImageFile(ImageFile.ImageFile):
+
+ format = "GBR"
+ format_description = "GIMP brush file"
+
+ def _open(self):
+
+ header_size = i32(self.fp.read(4))
+ version = i32(self.fp.read(4))
+ if header_size < 20 or version != 1:
+ raise SyntaxError("not a GIMP brush")
+
+ width = i32(self.fp.read(4))
+ height = i32(self.fp.read(4))
+ bytes = i32(self.fp.read(4))
+ if width <= 0 or height <= 0 or bytes != 1:
+ raise SyntaxError("not a GIMP brush")
+
+ comment = self.fp.read(header_size - 20)[:-1]
+
+ self.mode = "L"
+ self.size = width, height
+
+ self.info["comment"] = comment
+
+ # Since the brush is so small, we read the data immediately
+ self.data = self.fp.read(width * height)
+
+ def load(self):
+
+ if not self.data:
+ return
+
+ # create an image out of the brush data block
+ self.im = Image.core.new(self.mode, self.size)
+ self.im.frombytes(self.data)
+ self.data = b""
+
+#
+# registry
+
+Image.register_open("GBR", GbrImageFile, _accept)
+
+Image.register_extension("GBR", ".gbr")
diff --git a/lib/Python/Lib/PIL/GdImageFile.py b/lib/Python/Lib/PIL/GdImageFile.py
new file mode 100644
index 000000000..080153a9f
--- /dev/null
+++ b/lib/Python/Lib/PIL/GdImageFile.py
@@ -0,0 +1,92 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# GD file handling
+#
+# History:
+# 1996-04-12 fl Created
+#
+# Copyright (c) 1997 by Secret Labs AB.
+# Copyright (c) 1996 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+# NOTE: This format cannot be automatically recognized, so the
+# class is not registered for use with Image.open(). To open a
+# gd file, use the GdImageFile.open() function instead.
+
+# THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This
+# implementation is provided for convenience and demonstrational
+# purposes only.
+
+
+__version__ = "0.1"
+
+from PIL import ImageFile, ImagePalette, _binary
+from PIL._util import isPath
+
+try:
+ import builtins
+except ImportError:
+ import __builtin__
+ builtins = __builtin__
+
+i16 = _binary.i16be
+
+
+##
+# Image plugin for the GD uncompressed format. Note that this format
+# is not supported by the standard <b>Image.open</b> function. To use
+# this plugin, you have to import the <b>GdImageFile</b> module and
+# use the <b>GdImageFile.open</b> function.
+
+class GdImageFile(ImageFile.ImageFile):
+
+ format = "GD"
+ format_description = "GD uncompressed images"
+
+ def _open(self):
+
+ # Header
+ s = self.fp.read(775)
+
+ self.mode = "L" # FIXME: "P"
+ self.size = i16(s[0:2]), i16(s[2:4])
+
+ # transparency index
+ tindex = i16(s[5:7])
+ if tindex < 256:
+ self.info["transparent"] = tindex
+
+ self.palette = ImagePalette.raw("RGB", s[7:])
+
+ self.tile = [("raw", (0, 0)+self.size, 775, ("L", 0, -1))]
+
+
+##
+# Load texture from a GD image file.
+#
+# @param filename GD file name, or an opened file handle.
+# @param mode Optional mode. In this version, if the mode argument
+# is given, it must be "r".
+# @return An image instance.
+# @exception IOError If the image could not be read.
+
+def open(fp, mode="r"):
+
+ if mode != "r":
+ raise ValueError("bad mode")
+
+ if isPath(fp):
+ filename = fp
+ fp = builtins.open(fp, "rb")
+ else:
+ filename = ""
+
+ try:
+ return GdImageFile(fp, filename)
+ except SyntaxError:
+ raise IOError("cannot identify this image file")
diff --git a/lib/Python/Lib/PIL/GifImagePlugin.py b/lib/Python/Lib/PIL/GifImagePlugin.py
new file mode 100644
index 000000000..8db42e8e8
--- /dev/null
+++ b/lib/Python/Lib/PIL/GifImagePlugin.py
@@ -0,0 +1,543 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# GIF file handling
+#
+# History:
+# 1995-09-01 fl Created
+# 1996-12-14 fl Added interlace support
+# 1996-12-30 fl Added animation support
+# 1997-01-05 fl Added write support, fixed local colour map bug
+# 1997-02-23 fl Make sure to load raster data in getdata()
+# 1997-07-05 fl Support external decoder (0.4)
+# 1998-07-09 fl Handle all modes when saving (0.5)
+# 1998-07-15 fl Renamed offset attribute to avoid name clash
+# 2001-04-16 fl Added rewind support (seek to frame 0) (0.6)
+# 2001-04-17 fl Added palette optimization (0.7)
+# 2002-06-06 fl Added transparency support for save (0.8)
+# 2004-02-24 fl Disable interlacing for small images
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1995-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+
+__version__ = "0.9"
+
+
+# --------------------------------------------------------------------
+# Helpers
+
+i8 = _binary.i8
+i16 = _binary.i16le
+o8 = _binary.o8
+o16 = _binary.o16le
+
+
+# --------------------------------------------------------------------
+# Identify/read GIF files
+
+def _accept(prefix):
+ return prefix[:6] in [b"GIF87a", b"GIF89a"]
+
+
+##
+# Image plugin for GIF images. This plugin supports both GIF87 and
+# GIF89 images.
+
+class GifImageFile(ImageFile.ImageFile):
+
+ format = "GIF"
+ format_description = "Compuserve GIF"
+ global_palette = None
+
+ def data(self):
+ s = self.fp.read(1)
+ if s and i8(s):
+ return self.fp.read(i8(s))
+ return None
+
+ def _open(self):
+
+ # Screen
+ s = self.fp.read(13)
+ if s[:6] not in [b"GIF87a", b"GIF89a"]:
+ raise SyntaxError("not a GIF file")
+
+ self.info["version"] = s[:6]
+ self.size = i16(s[6:]), i16(s[8:])
+ self.tile = []
+ flags = i8(s[10])
+ bits = (flags & 7) + 1
+
+ if flags & 128:
+ # get global palette
+ self.info["background"] = i8(s[11])
+ # check if palette contains colour indices
+ p = self.fp.read(3 << bits)
+ for i in range(0, len(p), 3):
+ if not (i//3 == i8(p[i]) == i8(p[i+1]) == i8(p[i+2])):
+ p = ImagePalette.raw("RGB", p)
+ self.global_palette = self.palette = p
+ break
+
+ self.__fp = self.fp # FIXME: hack
+ self.__rewind = self.fp.tell()
+ self.seek(0) # get ready to read first frame
+
+ def seek(self, frame):
+
+ if frame == 0:
+ # rewind
+ self.__offset = 0
+ self.dispose = None
+ self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1
+ self.__frame = -1
+ self.__fp.seek(self.__rewind)
+ self._prev_im = None
+ self.disposal_method = 0
+ else:
+ # ensure that the previous frame was loaded
+ if not self.im:
+ self.load()
+
+ if frame != self.__frame + 1:
+ raise ValueError("cannot seek to frame %d" % frame)
+ self.__frame = frame
+
+ self.tile = []
+
+ self.fp = self.__fp
+ if self.__offset:
+ # backup to last frame
+ self.fp.seek(self.__offset)
+ while self.data():
+ pass
+ self.__offset = 0
+
+ if self.dispose:
+ self.im.paste(self.dispose, self.dispose_extent)
+
+ from copy import copy
+ self.palette = copy(self.global_palette)
+
+ while True:
+
+ s = self.fp.read(1)
+ if not s or s == b";":
+ break
+
+ elif s == b"!":
+ #
+ # extensions
+ #
+ s = self.fp.read(1)
+ block = self.data()
+ if i8(s) == 249:
+ #
+ # graphic control extension
+ #
+ flags = i8(block[0])
+ if flags & 1:
+ self.info["transparency"] = i8(block[3])
+ self.info["duration"] = i16(block[1:3]) * 10
+
+ # disposal method - find the value of bits 4 - 6
+ dispose_bits = 0b00011100 & flags
+ dispose_bits = dispose_bits >> 2
+ if dispose_bits:
+ # only set the dispose if it is not
+ # unspecified. I'm not sure if this is
+ # correct, but it seems to prevent the last
+ # frame from looking odd for some animations
+ self.disposal_method = dispose_bits
+ elif i8(s) == 255:
+ #
+ # application extension
+ #
+ self.info["extension"] = block, self.fp.tell()
+ if block[:11] == b"NETSCAPE2.0":
+ block = self.data()
+ if len(block) >= 3 and i8(block[0]) == 1:
+ self.info["loop"] = i16(block[1:3])
+ while self.data():
+ pass
+
+ elif s == b",":
+ #
+ # local image
+ #
+ s = self.fp.read(9)
+
+ # extent
+ x0, y0 = i16(s[0:]), i16(s[2:])
+ x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
+ self.dispose_extent = x0, y0, x1, y1
+ flags = i8(s[8])
+
+ interlace = (flags & 64) != 0
+
+ if flags & 128:
+ bits = (flags & 7) + 1
+ self.palette =\
+ ImagePalette.raw("RGB", self.fp.read(3 << bits))
+
+ # image data
+ bits = i8(self.fp.read(1))
+ self.__offset = self.fp.tell()
+ self.tile = [("gif",
+ (x0, y0, x1, y1),
+ self.__offset,
+ (bits, interlace))]
+ break
+
+ else:
+ pass
+ # raise IOError, "illegal GIF tag `%x`" % i8(s)
+
+ try:
+ if self.disposal_method < 2:
+ # do not dispose or none specified
+ self.dispose = None
+ elif self.disposal_method == 2:
+ # replace with background colour
+ self.dispose = Image.core.fill("P", self.size,
+ self.info["background"])
+ else:
+ # replace with previous contents
+ if self.im:
+ self.dispose = self.im.copy()
+
+ # only dispose the extent in this frame
+ if self.dispose:
+ self.dispose = self.dispose.crop(self.dispose_extent)
+ except (AttributeError, KeyError):
+ pass
+
+ if not self.tile:
+ # self.__fp = None
+ raise EOFError("no more images in GIF file")
+
+ self.mode = "L"
+ if self.palette:
+ self.mode = "P"
+
+ def tell(self):
+ return self.__frame
+
+ def load_end(self):
+ ImageFile.ImageFile.load_end(self)
+
+ # if the disposal method is 'do not dispose', transparent
+ # pixels should show the content of the previous frame
+ if self._prev_im and self.disposal_method == 1:
+ # we do this by pasting the updated area onto the previous
+ # frame which we then use as the current image content
+ updated = self.im.crop(self.dispose_extent)
+ self._prev_im.paste(updated, self.dispose_extent,
+ updated.convert('RGBA'))
+ self.im = self._prev_im
+ self._prev_im = self.im.copy()
+
+# --------------------------------------------------------------------
+# Write GIF files
+
+try:
+ import _imaging_gif
+except ImportError:
+ _imaging_gif = None
+
+RAWMODE = {
+ "1": "L",
+ "L": "L",
+ "P": "P",
+}
+
+
+def _save(im, fp, filename):
+
+ if _imaging_gif:
+ # call external driver
+ try:
+ _imaging_gif.save(im, fp, filename)
+ return
+ except IOError:
+ pass # write uncompressed file
+
+ if im.mode in RAWMODE:
+ im_out = im
+ else:
+ # convert on the fly (EXPERIMENTAL -- I'm not sure PIL
+ # should automatically convert images on save...)
+ if Image.getmodebase(im.mode) == "RGB":
+ palette_size = 256
+ if im.palette:
+ palette_size = len(im.palette.getdata()[1]) // 3
+ im_out = im.convert("P", palette=1, colors=palette_size)
+ else:
+ im_out = im.convert("L")
+
+ # header
+ try:
+ palette = im.encoderinfo["palette"]
+ except KeyError:
+ palette = None
+ im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
+
+ header, used_palette_colors = getheader(im_out, palette, im.encoderinfo)
+ for s in header:
+ fp.write(s)
+
+ flags = 0
+
+ try:
+ interlace = im.encoderinfo["interlace"]
+ except KeyError:
+ interlace = 1
+
+ # workaround for @PIL153
+ if min(im.size) < 16:
+ interlace = 0
+
+ if interlace:
+ flags = flags | 64
+
+ try:
+ transparency = im.encoderinfo["transparency"]
+ except KeyError:
+ pass
+ else:
+ transparency = int(transparency)
+ # optimize the block away if transparent color is not used
+ transparent_color_exists = True
+ # adjust the transparency index after optimize
+ if used_palette_colors is not None and len(used_palette_colors) < 256:
+ for i in range(len(used_palette_colors)):
+ if used_palette_colors[i] == transparency:
+ transparency = i
+ transparent_color_exists = True
+ break
+ else:
+ transparent_color_exists = False
+
+ # transparency extension block
+ if transparent_color_exists:
+ fp.write(b"!" +
+ o8(249) + # extension intro
+ o8(4) + # length
+ o8(1) + # transparency info present
+ o16(0) + # duration
+ o8(transparency) + # transparency index
+ o8(0))
+
+ # local image header
+ fp.write(b"," +
+ o16(0) + o16(0) + # bounding box
+ o16(im.size[0]) + # size
+ o16(im.size[1]) +
+ o8(flags) + # flags
+ o8(8)) # bits
+
+ im_out.encoderconfig = (8, interlace)
+ ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
+ RAWMODE[im_out.mode])])
+
+ fp.write(b"\0") # end of image data
+
+ fp.write(b";") # end of file
+
+ try:
+ fp.flush()
+ except:
+ pass
+
+
+def _save_netpbm(im, fp, filename):
+
+ #
+ # If you need real GIF compression and/or RGB quantization, you
+ # can use the external NETPBM/PBMPLUS utilities. See comments
+ # below for information on how to enable this.
+
+ import os
+ from subprocess import Popen, check_call, PIPE, CalledProcessError
+ import tempfile
+ file = im._dump()
+
+ if im.mode != "RGB":
+ with open(filename, 'wb') as f:
+ stderr = tempfile.TemporaryFile()
+ check_call(["ppmtogif", file], stdout=f, stderr=stderr)
+ else:
+ with open(filename, 'wb') as f:
+
+ # Pipe ppmquant output into ppmtogif
+ # "ppmquant 256 %s | ppmtogif > %s" % (file, filename)
+ quant_cmd = ["ppmquant", "256", file]
+ togif_cmd = ["ppmtogif"]
+ stderr = tempfile.TemporaryFile()
+ quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr)
+ stderr = tempfile.TemporaryFile()
+ togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f,
+ stderr=stderr)
+
+ # Allow ppmquant to receive SIGPIPE if ppmtogif exits
+ quant_proc.stdout.close()
+
+ retcode = quant_proc.wait()
+ if retcode:
+ raise CalledProcessError(retcode, quant_cmd)
+
+ retcode = togif_proc.wait()
+ if retcode:
+ raise CalledProcessError(retcode, togif_cmd)
+
+ try:
+ os.unlink(file)
+ except:
+ pass
+
+
+# --------------------------------------------------------------------
+# GIF utilities
+
+def getheader(im, palette=None, info=None):
+ """Return a list of strings representing a GIF header"""
+
+ optimize = info and info.get("optimize", 0)
+
+ # Header Block
+ # http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
+ header = [
+ b"GIF87a" + # signature + version
+ o16(im.size[0]) + # canvas width
+ o16(im.size[1]) # canvas height
+ ]
+
+ if im.mode == "P":
+ if palette and isinstance(palette, bytes):
+ source_palette = palette[:768]
+ else:
+ source_palette = im.im.getpalette("RGB")[:768]
+ else: # L-mode
+ if palette and isinstance(palette, bytes):
+ source_palette = palette[:768]
+ else:
+ source_palette = bytearray([i//3 for i in range(768)])
+
+ used_palette_colors = palette_bytes = None
+
+ if im.mode in ("P", "L") and optimize:
+ used_palette_colors = []
+
+ # check which colors are used
+ i = 0
+ for count in im.histogram():
+ if count:
+ used_palette_colors.append(i)
+ i += 1
+
+ # create the new palette if not every color is used
+ if len(used_palette_colors) < 256:
+ palette_bytes = b""
+ new_positions = {}
+
+ i = 0
+ # pick only the used colors from the palette
+ for oldPosition in used_palette_colors:
+ palette_bytes += source_palette[oldPosition*3:oldPosition*3+3]
+ new_positions[oldPosition] = i
+ i += 1
+
+ # replace the palette color id of all pixel with the new id
+ image_bytes = bytearray(im.tobytes())
+ for i in range(len(image_bytes)):
+ image_bytes[i] = new_positions[image_bytes[i]]
+ im.frombytes(bytes(image_bytes))
+ new_palette_bytes = (palette_bytes +
+ (768 - len(palette_bytes)) * b'\x00')
+ im.putpalette(new_palette_bytes)
+ im.palette = ImagePalette.ImagePalette("RGB",
+ palette=palette_bytes,
+ size=len(palette_bytes))
+
+ if not palette_bytes:
+ palette_bytes = source_palette
+
+ # Logical Screen Descriptor
+ # calculate the palette size for the header
+ import math
+ color_table_size = int(math.ceil(math.log(len(palette_bytes)//3, 2)))-1
+ if color_table_size < 0:
+ color_table_size = 0
+ # size of global color table + global color table flag
+ header.append(o8(color_table_size + 128))
+ # background + reserved/aspect
+ header.append(o8(0) + o8(0))
+ # end of Logical Screen Descriptor
+
+ # add the missing amount of bytes
+ # the palette has to be 2<<n in size
+ actual_target_size_diff = (2 << color_table_size) - len(palette_bytes)//3
+ if actual_target_size_diff > 0:
+ palette_bytes += o8(0) * 3 * actual_target_size_diff
+
+ # Header + Logical Screen Descriptor + Global Color Table
+ header.append(palette_bytes)
+ return header, used_palette_colors
+
+
+def getdata(im, offset=(0, 0), **params):
+ """Return a list of strings representing this image.
+ The first string is a local image header, the rest contains
+ encoded image data."""
+
+ class Collector:
+ data = []
+
+ def write(self, data):
+ self.data.append(data)
+
+ im.load() # make sure raster data is available
+
+ fp = Collector()
+
+ try:
+ im.encoderinfo = params
+
+ # local image header
+ fp.write(b"," +
+ o16(offset[0]) + # offset
+ o16(offset[1]) +
+ o16(im.size[0]) + # size
+ o16(im.size[1]) +
+ o8(0) + # flags
+ o8(8)) # bits
+
+ ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
+
+ fp.write(b"\0") # end of image data
+
+ finally:
+ del im.encoderinfo
+
+ return fp.data
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(GifImageFile.format, GifImageFile, _accept)
+Image.register_save(GifImageFile.format, _save)
+Image.register_extension(GifImageFile.format, ".gif")
+Image.register_mime(GifImageFile.format, "image/gif")
+
+#
+# Uncomment the following line if you wish to use NETPBM/PBMPLUS
+# instead of the built-in "uncompressed" GIF encoder
+
+# Image.register_save(GifImageFile.format, _save_netpbm)
diff --git a/lib/Python/Lib/PIL/GimpGradientFile.py b/lib/Python/Lib/PIL/GimpGradientFile.py
new file mode 100644
index 000000000..696f425f1
--- /dev/null
+++ b/lib/Python/Lib/PIL/GimpGradientFile.py
@@ -0,0 +1,137 @@
+#
+# Python Imaging Library
+# $Id$
+#
+# stuff to read (and render) GIMP gradient files
+#
+# History:
+# 97-08-23 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from math import pi, log, sin, sqrt
+from PIL._binary import o8
+
+# --------------------------------------------------------------------
+# Stuff to translate curve segments to palette values (derived from
+# the corresponding code in GIMP, written by Federico Mena Quintero.
+# See the GIMP distribution for more information.)
+#
+
+EPSILON = 1e-10
+
+
+def linear(middle, pos):
+ if pos <= middle:
+ if middle < EPSILON:
+ return 0.0
+ else:
+ return 0.5 * pos / middle
+ else:
+ pos = pos - middle
+ middle = 1.0 - middle
+ if middle < EPSILON:
+ return 1.0
+ else:
+ return 0.5 + 0.5 * pos / middle
+
+
+def curved(middle, pos):
+ return pos ** (log(0.5) / log(max(middle, EPSILON)))
+
+
+def sine(middle, pos):
+ return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
+
+
+def sphere_increasing(middle, pos):
+ return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
+
+
+def sphere_decreasing(middle, pos):
+ return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
+
+SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
+
+
+class GradientFile:
+
+ gradient = None
+
+ def getpalette(self, entries=256):
+
+ palette = []
+
+ ix = 0
+ x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
+
+ for i in range(entries):
+
+ x = i / float(entries-1)
+
+ while x1 < x:
+ ix += 1
+ x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
+
+ w = x1 - x0
+
+ if w < EPSILON:
+ scale = segment(0.5, 0.5)
+ else:
+ scale = segment((xm - x0) / w, (x - x0) / w)
+
+ # expand to RGBA
+ r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
+ g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
+ b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
+ a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
+
+ # add to palette
+ palette.append(r + g + b + a)
+
+ return b"".join(palette), "RGBA"
+
+
+##
+# File handler for GIMP's gradient format.
+
+class GimpGradientFile(GradientFile):
+
+ def __init__(self, fp):
+
+ if fp.readline()[:13] != b"GIMP Gradient":
+ raise SyntaxError("not a GIMP gradient file")
+
+ line = fp.readline()
+
+ # GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
+ if line.startswith(b"Name: "):
+ line = fp.readline().strip()
+
+ count = int(line)
+
+ gradient = []
+
+ for i in range(count):
+
+ s = fp.readline().split()
+ w = [float(x) for x in s[:11]]
+
+ x0, x1 = w[0], w[2]
+ xm = w[1]
+ rgb0 = w[3:7]
+ rgb1 = w[7:11]
+
+ segment = SEGMENTS[int(s[11])]
+ cspace = int(s[12])
+
+ if cspace != 0:
+ raise IOError("cannot handle HSV colour space")
+
+ gradient.append((x0, x1, xm, rgb0, rgb1, segment))
+
+ self.gradient = gradient
diff --git a/lib/Python/Lib/PIL/GimpPaletteFile.py b/lib/Python/Lib/PIL/GimpPaletteFile.py
new file mode 100644
index 000000000..a066c80cc
--- /dev/null
+++ b/lib/Python/Lib/PIL/GimpPaletteFile.py
@@ -0,0 +1,62 @@
+#
+# Python Imaging Library
+# $Id$
+#
+# stuff to read GIMP palette files
+#
+# History:
+# 1997-08-23 fl Created
+# 2004-09-07 fl Support GIMP 2.0 palette files.
+#
+# Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
+# Copyright (c) Fredrik Lundh 1997-2004.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import re
+from PIL._binary import o8
+
+
+##
+# File handler for GIMP's palette format.
+
+class GimpPaletteFile:
+
+ rawmode = "RGB"
+
+ def __init__(self, fp):
+
+ self.palette = [o8(i)*3 for i in range(256)]
+
+ if fp.readline()[:12] != b"GIMP Palette":
+ raise SyntaxError("not a GIMP palette file")
+
+ i = 0
+
+ while i <= 255:
+
+ s = fp.readline()
+
+ if not s:
+ break
+ # skip fields and comment lines
+ if re.match(b"\w+:|#", s):
+ continue
+ if len(s) > 100:
+ raise SyntaxError("bad palette file")
+
+ v = tuple(map(int, s.split()[:3]))
+ if len(v) != 3:
+ raise ValueError("bad palette entry")
+
+ if 0 <= i <= 255:
+ self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
+
+ i += 1
+
+ self.palette = b"".join(self.palette)
+
+ def getpalette(self):
+
+ return self.palette, self.rawmode
diff --git a/lib/Python/Lib/PIL/GribStubImagePlugin.py b/lib/Python/Lib/PIL/GribStubImagePlugin.py
new file mode 100644
index 000000000..8ffad8100
--- /dev/null
+++ b/lib/Python/Lib/PIL/GribStubImagePlugin.py
@@ -0,0 +1,72 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# GRIB stub adapter
+#
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFile
+
+_handler = None
+
+
+##
+# Install application-specific GRIB image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[0:4] == b"GRIB" and prefix[7] == b'\x01'
+
+
+class GribStubImageFile(ImageFile.StubImageFile):
+
+ format = "GRIB"
+ format_description = "GRIB"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ raise SyntaxError("Not a GRIB file")
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("GRIB save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept)
+Image.register_save(GribStubImageFile.format, _save)
+
+Image.register_extension(GribStubImageFile.format, ".grib")
diff --git a/lib/Python/Lib/PIL/Hdf5StubImagePlugin.py b/lib/Python/Lib/PIL/Hdf5StubImagePlugin.py
new file mode 100644
index 000000000..f7945be7e
--- /dev/null
+++ b/lib/Python/Lib/PIL/Hdf5StubImagePlugin.py
@@ -0,0 +1,73 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# HDF5 stub adapter
+#
+# Copyright (c) 2000-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFile
+
+_handler = None
+
+
+##
+# Install application-specific HDF5 image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+def _accept(prefix):
+ return prefix[:8] == b"\x89HDF\r\n\x1a\n"
+
+
+class HDF5StubImageFile(ImageFile.StubImageFile):
+
+ format = "HDF5"
+ format_description = "HDF5"
+
+ def _open(self):
+
+ offset = self.fp.tell()
+
+ if not _accept(self.fp.read(8)):
+ raise SyntaxError("Not an HDF file")
+
+ self.fp.seek(offset)
+
+ # make something up
+ self.mode = "F"
+ self.size = 1, 1
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("HDF5 save handler not installed")
+ _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
+Image.register_save(HDF5StubImageFile.format, _save)
+
+Image.register_extension(HDF5StubImageFile.format, ".h5")
+Image.register_extension(HDF5StubImageFile.format, ".hdf")
diff --git a/lib/Python/Lib/PIL/IcnsImagePlugin.py b/lib/Python/Lib/PIL/IcnsImagePlugin.py
new file mode 100644
index 000000000..e88b84985
--- /dev/null
+++ b/lib/Python/Lib/PIL/IcnsImagePlugin.py
@@ -0,0 +1,311 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Mac OS X icns file decoder, based on icns.py by Bob Ippolito.
+#
+# history:
+# 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies.
+#
+# Copyright (c) 2004 by Bob Ippolito.
+# Copyright (c) 2004 by Secret Labs.
+# Copyright (c) 2004 by Fredrik Lundh.
+# Copyright (c) 2014 by Alastair Houghton.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFile, PngImagePlugin, _binary
+import io
+import struct
+
+enable_jpeg2k = hasattr(Image.core, 'jp2klib_version')
+if enable_jpeg2k:
+ from PIL import Jpeg2KImagePlugin
+
+i8 = _binary.i8
+
+HEADERSIZE = 8
+
+
+def nextheader(fobj):
+ return struct.unpack('>4sI', fobj.read(HEADERSIZE))
+
+
+def read_32t(fobj, start_length, size):
+ # The 128x128 icon seems to have an extra header for some reason.
+ (start, length) = start_length
+ fobj.seek(start)
+ sig = fobj.read(4)
+ if sig != b'\x00\x00\x00\x00':
+ raise SyntaxError('Unknown signature, expecting 0x00000000')
+ return read_32(fobj, (start + 4, length - 4), size)
+
+
+def read_32(fobj, start_length, size):
+ """
+ Read a 32bit RGB icon resource. Seems to be either uncompressed or
+ an RLE packbits-like scheme.
+ """
+ (start, length) = start_length
+ fobj.seek(start)
+ pixel_size = (size[0] * size[2], size[1] * size[2])
+ sizesq = pixel_size[0] * pixel_size[1]
+ if length == sizesq * 3:
+ # uncompressed ("RGBRGBGB")
+ indata = fobj.read(length)
+ im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1)
+ else:
+ # decode image
+ im = Image.new("RGB", pixel_size, None)
+ for band_ix in range(3):
+ data = []
+ bytesleft = sizesq
+ while bytesleft > 0:
+ byte = fobj.read(1)
+ if not byte:
+ break
+ byte = i8(byte)
+ if byte & 0x80:
+ blocksize = byte - 125
+ byte = fobj.read(1)
+ for i in range(blocksize):
+ data.append(byte)
+ else:
+ blocksize = byte + 1
+ data.append(fobj.read(blocksize))
+ bytesleft -= blocksize
+ if bytesleft <= 0:
+ break
+ if bytesleft != 0:
+ raise SyntaxError(
+ "Error reading channel [%r left]" % bytesleft
+ )
+ band = Image.frombuffer(
+ "L", pixel_size, b"".join(data), "raw", "L", 0, 1
+ )
+ im.im.putband(band.im, band_ix)
+ return {"RGB": im}
+
+
+def read_mk(fobj, start_length, size):
+ # Alpha masks seem to be uncompressed
+ (start, length) = start_length
+ fobj.seek(start)
+ pixel_size = (size[0] * size[2], size[1] * size[2])
+ sizesq = pixel_size[0] * pixel_size[1]
+ band = Image.frombuffer(
+ "L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1
+ )
+ return {"A": band}
+
+
+def read_png_or_jpeg2000(fobj, start_length, size):
+ (start, length) = start_length
+ fobj.seek(start)
+ sig = fobj.read(12)
+ if sig[:8] == b'\x89PNG\x0d\x0a\x1a\x0a':
+ fobj.seek(start)
+ im = PngImagePlugin.PngImageFile(fobj)
+ return {"RGBA": im}
+ elif sig[:4] == b'\xff\x4f\xff\x51' \
+ or sig[:4] == b'\x0d\x0a\x87\x0a' \
+ or sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
+ if not enable_jpeg2k:
+ raise ValueError('Unsupported icon subimage format (rebuild PIL '
+ 'with JPEG 2000 support to fix this)')
+ # j2k, jpc or j2c
+ fobj.seek(start)
+ jp2kstream = fobj.read(length)
+ f = io.BytesIO(jp2kstream)
+ im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
+ if im.mode != 'RGBA':
+ im = im.convert('RGBA')
+ return {"RGBA": im}
+ else:
+ raise ValueError('Unsupported icon subimage format')
+
+
+class IcnsFile:
+
+ SIZES = {
+ (512, 512, 2): [
+ (b'ic10', read_png_or_jpeg2000),
+ ],
+ (512, 512, 1): [
+ (b'ic09', read_png_or_jpeg2000),
+ ],
+ (256, 256, 2): [
+ (b'ic14', read_png_or_jpeg2000),
+ ],
+ (256, 256, 1): [
+ (b'ic08', read_png_or_jpeg2000),
+ ],
+ (128, 128, 2): [
+ (b'ic13', read_png_or_jpeg2000),
+ ],
+ (128, 128, 1): [
+ (b'ic07', read_png_or_jpeg2000),
+ (b'it32', read_32t),
+ (b't8mk', read_mk),
+ ],
+ (64, 64, 1): [
+ (b'icp6', read_png_or_jpeg2000),
+ ],
+ (32, 32, 2): [
+ (b'ic12', read_png_or_jpeg2000),
+ ],
+ (48, 48, 1): [
+ (b'ih32', read_32),
+ (b'h8mk', read_mk),
+ ],
+ (32, 32, 1): [
+ (b'icp5', read_png_or_jpeg2000),
+ (b'il32', read_32),
+ (b'l8mk', read_mk),
+ ],
+ (16, 16, 2): [
+ (b'ic11', read_png_or_jpeg2000),
+ ],
+ (16, 16, 1): [
+ (b'icp4', read_png_or_jpeg2000),
+ (b'is32', read_32),
+ (b's8mk', read_mk),
+ ],
+ }
+
+ def __init__(self, fobj):
+ """
+ fobj is a file-like object as an icns resource
+ """
+ # signature : (start, length)
+ self.dct = dct = {}
+ self.fobj = fobj
+ sig, filesize = nextheader(fobj)
+ if sig != b'icns':
+ raise SyntaxError('not an icns file')
+ i = HEADERSIZE
+ while i < filesize:
+ sig, blocksize = nextheader(fobj)
+ if blocksize <= 0:
+ raise SyntaxError('invalid block header')
+ i += HEADERSIZE
+ blocksize -= HEADERSIZE
+ dct[sig] = (i, blocksize)
+ fobj.seek(blocksize, 1)
+ i += blocksize
+
+ def itersizes(self):
+ sizes = []
+ for size, fmts in self.SIZES.items():
+ for (fmt, reader) in fmts:
+ if fmt in self.dct:
+ sizes.append(size)
+ break
+ return sizes
+
+ def bestsize(self):
+ sizes = self.itersizes()
+ if not sizes:
+ raise SyntaxError("No 32bit icon resources found")
+ return max(sizes)
+
+ def dataforsize(self, size):
+ """
+ Get an icon resource as {channel: array}. Note that
+ the arrays are bottom-up like windows bitmaps and will likely
+ need to be flipped or transposed in some way.
+ """
+ dct = {}
+ for code, reader in self.SIZES[size]:
+ desc = self.dct.get(code)
+ if desc is not None:
+ dct.update(reader(self.fobj, desc, size))
+ return dct
+
+ def getimage(self, size=None):
+ if size is None:
+ size = self.bestsize()
+ if len(size) == 2:
+ size = (size[0], size[1], 1)
+ channels = self.dataforsize(size)
+
+ im = channels.get('RGBA', None)
+ if im:
+ return im
+
+ im = channels.get("RGB").copy()
+ try:
+ im.putalpha(channels["A"])
+ except KeyError:
+ pass
+ return im
+
+
+##
+# Image plugin for Mac OS icons.
+
+class IcnsImageFile(ImageFile.ImageFile):
+ """
+ PIL read-only image support for Mac OS .icns files.
+ Chooses the best resolution, but will possibly load
+ a different size image if you mutate the size attribute
+ before calling 'load'.
+
+ The info dictionary has a key 'sizes' that is a list
+ of sizes that the icns file has.
+ """
+
+ format = "ICNS"
+ format_description = "Mac OS icns resource"
+
+ def _open(self):
+ self.icns = IcnsFile(self.fp)
+ self.mode = 'RGBA'
+ self.best_size = self.icns.bestsize()
+ self.size = (self.best_size[0] * self.best_size[2],
+ self.best_size[1] * self.best_size[2])
+ self.info['sizes'] = self.icns.itersizes()
+ # Just use this to see if it's loaded or not yet.
+ self.tile = ('',)
+
+ def load(self):
+ if len(self.size) == 3:
+ self.best_size = self.size
+ self.size = (self.best_size[0] * self.best_size[2],
+ self.best_size[1] * self.best_size[2])
+
+ Image.Image.load(self)
+ if not self.tile:
+ return
+ self.load_prepare()
+ # This is likely NOT the best way to do it, but whatever.
+ im = self.icns.getimage(self.best_size)
+
+ # If this is a PNG or JPEG 2000, it won't be loaded yet
+ im.load()
+
+ self.im = im.im
+ self.mode = im.mode
+ self.size = im.size
+ self.fp = None
+ self.icns = None
+ self.tile = ()
+ self.load_end()
+
+Image.register_open("ICNS", IcnsImageFile, lambda x: x[:4] == b'icns')
+Image.register_extension("ICNS", '.icns')
+
+if __name__ == '__main__':
+ import os
+ import sys
+ imf = IcnsImageFile(open(sys.argv[1], 'rb'))
+ for size in imf.info['sizes']:
+ imf.size = size
+ imf.load()
+ im = imf.im
+ im.save('out-%s-%s-%s.png' % size)
+ im = Image.open(open(sys.argv[1], "rb"))
+ im.save("out.png")
+ if sys.platform == 'windows':
+ os.startfile("out.png")
diff --git a/lib/Python/Lib/PIL/IcoImagePlugin.py b/lib/Python/Lib/PIL/IcoImagePlugin.py
new file mode 100644
index 000000000..b4817db27
--- /dev/null
+++ b/lib/Python/Lib/PIL/IcoImagePlugin.py
@@ -0,0 +1,284 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Windows Icon support for PIL
+#
+# History:
+# 96-05-27 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
+# <casadebender@gmail.com>.
+# https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
+#
+# Icon format references:
+# * http://en.wikipedia.org/wiki/ICO_(file_format)
+# * http://msdn.microsoft.com/en-us/library/ms997538.aspx
+
+
+__version__ = "0.1"
+
+import struct
+from io import BytesIO
+
+from PIL import Image, ImageFile, BmpImagePlugin, PngImagePlugin, _binary
+from math import log, ceil
+
+#
+# --------------------------------------------------------------------
+
+i8 = _binary.i8
+i16 = _binary.i16le
+i32 = _binary.i32le
+
+_MAGIC = b"\0\0\1\0"
+
+
+def _save(im, fp, filename):
+ fp.write(_MAGIC) # (2+2)
+ sizes = im.encoderinfo.get("sizes",
+ [(16, 16), (24, 24), (32, 32), (48, 48),
+ (64, 64), (128, 128), (255, 255)])
+ width, height = im.size
+ filter(lambda x: False if (x[0] > width or x[1] > height or
+ x[0] > 255 or x[1] > 255) else True, sizes)
+ sizes = sorted(sizes, key=lambda x: x[0])
+ fp.write(struct.pack("H", len(sizes))) # idCount(2)
+ offset = fp.tell() + len(sizes)*16
+ for size in sizes:
+ width, height = size
+ fp.write(struct.pack("B", width)) # bWidth(1)
+ fp.write(struct.pack("B", height)) # bHeight(1)
+ fp.write(b"\0") # bColorCount(1)
+ fp.write(b"\0") # bReserved(1)
+ fp.write(b"\0\0") # wPlanes(2)
+ fp.write(struct.pack("H", 32)) # wBitCount(2)
+
+ image_io = BytesIO()
+ tmp = im.copy()
+ tmp.thumbnail(size, Image.LANCZOS)
+ tmp.save(image_io, "png")
+ image_io.seek(0)
+ image_bytes = image_io.read()
+ bytes_len = len(image_bytes)
+ fp.write(struct.pack("I", bytes_len)) # dwBytesInRes(4)
+ fp.write(struct.pack("I", offset)) # dwImageOffset(4)
+ current = fp.tell()
+ fp.seek(offset)
+ fp.write(image_bytes)
+ offset = offset + bytes_len
+ fp.seek(current)
+
+
+def _accept(prefix):
+ return prefix[:4] == _MAGIC
+
+
+class IcoFile:
+ def __init__(self, buf):
+ """
+ Parse image from file-like object containing ico file data
+ """
+
+ # check magic
+ s = buf.read(6)
+ if not _accept(s):
+ raise SyntaxError("not an ICO file")
+
+ self.buf = buf
+ self.entry = []
+
+ # Number of items in file
+ self.nb_items = i16(s[4:])
+
+ # Get headers for each item
+ for i in range(self.nb_items):
+ s = buf.read(16)
+
+ icon_header = {
+ 'width': i8(s[0]),
+ 'height': i8(s[1]),
+ 'nb_color': i8(s[2]), # No. of colors in image (0 if >=8bpp)
+ 'reserved': i8(s[3]),
+ 'planes': i16(s[4:]),
+ 'bpp': i16(s[6:]),
+ 'size': i32(s[8:]),
+ 'offset': i32(s[12:])
+ }
+
+ # See Wikipedia
+ for j in ('width', 'height'):
+ if not icon_header[j]:
+ icon_header[j] = 256
+
+ # See Wikipedia notes about color depth.
+ # We need this just to differ images with equal sizes
+ icon_header['color_depth'] = (icon_header['bpp'] or
+ (icon_header['nb_color'] != 0 and
+ ceil(log(icon_header['nb_color'],
+ 2))) or 256)
+
+ icon_header['dim'] = (icon_header['width'], icon_header['height'])
+ icon_header['square'] = (icon_header['width'] *
+ icon_header['height'])
+
+ self.entry.append(icon_header)
+
+ self.entry = sorted(self.entry, key=lambda x: x['color_depth'])
+ # ICO images are usually squares
+ # self.entry = sorted(self.entry, key=lambda x: x['width'])
+ self.entry = sorted(self.entry, key=lambda x: x['square'])
+ self.entry.reverse()
+
+ def sizes(self):
+ """
+ Get a list of all available icon sizes and color depths.
+ """
+ return set((h['width'], h['height']) for h in self.entry)
+
+ def getimage(self, size, bpp=False):
+ """
+ Get an image from the icon
+ """
+ for (i, h) in enumerate(self.entry):
+ if size == h['dim'] and (bpp is False or bpp == h['color_depth']):
+ return self.frame(i)
+ return self.frame(0)
+
+ def frame(self, idx):
+ """
+ Get an image from frame idx
+ """
+
+ header = self.entry[idx]
+
+ self.buf.seek(header['offset'])
+ data = self.buf.read(8)
+ self.buf.seek(header['offset'])
+
+ if data[:8] == PngImagePlugin._MAGIC:
+ # png frame
+ im = PngImagePlugin.PngImageFile(self.buf)
+ else:
+ # XOR + AND mask bmp frame
+ im = BmpImagePlugin.DibImageFile(self.buf)
+
+ # change tile dimension to only encompass XOR image
+ im.size = (im.size[0], int(im.size[1] / 2))
+ d, e, o, a = im.tile[0]
+ im.tile[0] = d, (0, 0) + im.size, o, a
+
+ # figure out where AND mask image starts
+ mode = a[0]
+ bpp = 8
+ for k in BmpImagePlugin.BIT2MODE.keys():
+ if mode == BmpImagePlugin.BIT2MODE[k][1]:
+ bpp = k
+ break
+
+ if 32 == bpp:
+ # 32-bit color depth icon image allows semitransparent areas
+ # PIL's DIB format ignores transparency bits, recover them.
+ # The DIB is packed in BGRX byte order where X is the alpha
+ # channel.
+
+ # Back up to start of bmp data
+ self.buf.seek(o)
+ # extract every 4th byte (eg. 3,7,11,15,...)
+ alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4]
+
+ # convert to an 8bpp grayscale image
+ mask = Image.frombuffer(
+ 'L', # 8bpp
+ im.size, # (w, h)
+ alpha_bytes, # source chars
+ 'raw', # raw decoder
+ ('L', 0, -1) # 8bpp inverted, unpadded, reversed
+ )
+ else:
+ # get AND image from end of bitmap
+ w = im.size[0]
+ if (w % 32) > 0:
+ # bitmap row data is aligned to word boundaries
+ w += 32 - (im.size[0] % 32)
+
+ # the total mask data is
+ # padded row size * height / bits per char
+
+ and_mask_offset = o + int(im.size[0] * im.size[1] *
+ (bpp / 8.0))
+ total_bytes = int((w * im.size[1]) / 8)
+
+ self.buf.seek(and_mask_offset)
+ maskData = self.buf.read(total_bytes)
+
+ # convert raw data to image
+ mask = Image.frombuffer(
+ '1', # 1 bpp
+ im.size, # (w, h)
+ maskData, # source chars
+ 'raw', # raw decoder
+ ('1;I', int(w/8), -1) # 1bpp inverted, padded, reversed
+ )
+
+ # now we have two images, im is XOR image and mask is AND image
+
+ # apply mask image as alpha channel
+ im = im.convert('RGBA')
+ im.putalpha(mask)
+
+ return im
+
+
+##
+# Image plugin for Windows Icon files.
+
+class IcoImageFile(ImageFile.ImageFile):
+ """
+ PIL read-only image support for Microsoft Windows .ico files.
+
+ By default the largest resolution image in the file will be loaded. This
+ can be changed by altering the 'size' attribute before calling 'load'.
+
+ The info dictionary has a key 'sizes' that is a list of the sizes available
+ in the icon file.
+
+ Handles classic, XP and Vista icon formats.
+
+ This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis
+ <casadebender@gmail.com>.
+ https://code.google.com/p/casadebender/wiki/Win32IconImagePlugin
+ """
+ format = "ICO"
+ format_description = "Windows Icon"
+
+ def _open(self):
+ self.ico = IcoFile(self.fp)
+ self.info['sizes'] = self.ico.sizes()
+ self.size = self.ico.entry[0]['dim']
+ self.load()
+
+ def load(self):
+ im = self.ico.getimage(self.size)
+ # if tile is PNG, it won't really be loaded yet
+ im.load()
+ self.im = im.im
+ self.mode = im.mode
+ self.size = im.size
+
+ def load_seek(self):
+ # Flag the ImageFile.Parser so that it
+ # just does all the decode at the end.
+ pass
+#
+# --------------------------------------------------------------------
+
+Image.register_open("ICO", IcoImageFile, _accept)
+Image.register_save("ICO", _save)
+Image.register_extension("ICO", ".ico")
diff --git a/lib/Python/Lib/PIL/ImImagePlugin.py b/lib/Python/Lib/PIL/ImImagePlugin.py
new file mode 100644
index 000000000..4266f8315
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImImagePlugin.py
@@ -0,0 +1,347 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# IFUNC IM file handling for PIL
+#
+# history:
+# 1995-09-01 fl Created.
+# 1997-01-03 fl Save palette images
+# 1997-01-08 fl Added sequence support
+# 1997-01-23 fl Added P and RGB save support
+# 1997-05-31 fl Read floating point images
+# 1997-06-22 fl Save floating point images
+# 1997-08-27 fl Read and save 1-bit images
+# 1998-06-25 fl Added support for RGB+LUT images
+# 1998-07-02 fl Added support for YCC images
+# 1998-07-15 fl Renamed offset attribute to avoid name clash
+# 1998-12-29 fl Added I;16 support
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
+# 2003-09-26 fl Added LA/PA support
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2001 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.7"
+
+import re
+from PIL import Image, ImageFile, ImagePalette
+from PIL._binary import i8
+
+
+# --------------------------------------------------------------------
+# Standard tags
+
+COMMENT = "Comment"
+DATE = "Date"
+EQUIPMENT = "Digitalization equipment"
+FRAMES = "File size (no of images)"
+LUT = "Lut"
+NAME = "Name"
+SCALE = "Scale (x,y)"
+SIZE = "Image size (x*y)"
+MODE = "Image type"
+
+TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
+ SCALE: 0, SIZE: 0, MODE: 0}
+
+OPEN = {
+ # ifunc93/p3cfunc formats
+ "0 1 image": ("1", "1"),
+ "L 1 image": ("1", "1"),
+ "Greyscale image": ("L", "L"),
+ "Grayscale image": ("L", "L"),
+ "RGB image": ("RGB", "RGB;L"),
+ "RLB image": ("RGB", "RLB"),
+ "RYB image": ("RGB", "RLB"),
+ "B1 image": ("1", "1"),
+ "B2 image": ("P", "P;2"),
+ "B4 image": ("P", "P;4"),
+ "X 24 image": ("RGB", "RGB"),
+ "L 32 S image": ("I", "I;32"),
+ "L 32 F image": ("F", "F;32"),
+ # old p3cfunc formats
+ "RGB3 image": ("RGB", "RGB;T"),
+ "RYB3 image": ("RGB", "RYB;T"),
+ # extensions
+ "LA image": ("LA", "LA;L"),
+ "RGBA image": ("RGBA", "RGBA;L"),
+ "RGBX image": ("RGBX", "RGBX;L"),
+ "CMYK image": ("CMYK", "CMYK;L"),
+ "YCC image": ("YCbCr", "YCbCr;L"),
+}
+
+# ifunc95 extensions
+for i in ["8", "8S", "16", "16S", "32", "32F"]:
+ OPEN["L %s image" % i] = ("F", "F;%s" % i)
+ OPEN["L*%s image" % i] = ("F", "F;%s" % i)
+for i in ["16", "16L", "16B"]:
+ OPEN["L %s image" % i] = ("I;%s" % i, "I;%s" % i)
+ OPEN["L*%s image" % i] = ("I;%s" % i, "I;%s" % i)
+for i in ["32S"]:
+ OPEN["L %s image" % i] = ("I", "I;%s" % i)
+ OPEN["L*%s image" % i] = ("I", "I;%s" % i)
+for i in range(2, 33):
+ OPEN["L*%s image" % i] = ("F", "F;%s" % i)
+
+
+# --------------------------------------------------------------------
+# Read IM directory
+
+split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
+
+
+def number(s):
+ try:
+ return int(s)
+ except ValueError:
+ return float(s)
+
+
+##
+# Image plugin for the IFUNC IM file format.
+
+class ImImageFile(ImageFile.ImageFile):
+
+ format = "IM"
+ format_description = "IFUNC Image Memory"
+
+ def _open(self):
+
+ # Quick rejection: if there's not an LF among the first
+ # 100 bytes, this is (probably) not a text header.
+
+ if b"\n" not in self.fp.read(100):
+ raise SyntaxError("not an IM file")
+ self.fp.seek(0)
+
+ n = 0
+
+ # Default values
+ self.info[MODE] = "L"
+ self.info[SIZE] = (512, 512)
+ self.info[FRAMES] = 1
+
+ self.rawmode = "L"
+
+ while True:
+
+ s = self.fp.read(1)
+
+ # Some versions of IFUNC uses \n\r instead of \r\n...
+ if s == b"\r":
+ continue
+
+ if not s or s == b'\0' or s == b'\x1A':
+ break
+
+ # FIXME: this may read whole file if not a text file
+ s = s + self.fp.readline()
+
+ if len(s) > 100:
+ raise SyntaxError("not an IM file")
+
+ if s[-2:] == b'\r\n':
+ s = s[:-2]
+ elif s[-1:] == b'\n':
+ s = s[:-1]
+
+ try:
+ m = split.match(s)
+ except re.error as v:
+ raise SyntaxError("not an IM file")
+
+ if m:
+
+ k, v = m.group(1, 2)
+
+ # Don't know if this is the correct encoding,
+ # but a decent guess (I guess)
+ k = k.decode('latin-1', 'replace')
+ v = v.decode('latin-1', 'replace')
+
+ # Convert value as appropriate
+ if k in [FRAMES, SCALE, SIZE]:
+ v = v.replace("*", ",")
+ v = tuple(map(number, v.split(",")))
+ if len(v) == 1:
+ v = v[0]
+ elif k == MODE and v in OPEN:
+ v, self.rawmode = OPEN[v]
+
+ # Add to dictionary. Note that COMMENT tags are
+ # combined into a list of strings.
+ if k == COMMENT:
+ if k in self.info:
+ self.info[k].append(v)
+ else:
+ self.info[k] = [v]
+ else:
+ self.info[k] = v
+
+ if k in TAGS:
+ n += 1
+
+ else:
+
+ raise SyntaxError("Syntax error in IM header: " +
+ s.decode('ascii', 'replace'))
+
+ if not n:
+ raise SyntaxError("Not an IM file")
+
+ # Basic attributes
+ self.size = self.info[SIZE]
+ self.mode = self.info[MODE]
+
+ # Skip forward to start of image data
+ while s and s[0:1] != b'\x1A':
+ s = self.fp.read(1)
+ if not s:
+ raise SyntaxError("File truncated")
+
+ if LUT in self.info:
+ # convert lookup table to palette or lut attribute
+ palette = self.fp.read(768)
+ greyscale = 1 # greyscale palette
+ linear = 1 # linear greyscale palette
+ for i in range(256):
+ if palette[i] == palette[i+256] == palette[i+512]:
+ if i8(palette[i]) != i:
+ linear = 0
+ else:
+ greyscale = 0
+ if self.mode == "L" or self.mode == "LA":
+ if greyscale:
+ if not linear:
+ self.lut = [i8(c) for c in palette[:256]]
+ else:
+ if self.mode == "L":
+ self.mode = self.rawmode = "P"
+ elif self.mode == "LA":
+ self.mode = self.rawmode = "PA"
+ self.palette = ImagePalette.raw("RGB;L", palette)
+ elif self.mode == "RGB":
+ if not greyscale or not linear:
+ self.lut = [i8(c) for c in palette]
+
+ self.frame = 0
+
+ self.__offset = offs = self.fp.tell()
+
+ self.__fp = self.fp # FIXME: hack
+
+ if self.rawmode[:2] == "F;":
+
+ # ifunc95 formats
+ try:
+ # use bit decoder (if necessary)
+ bits = int(self.rawmode[2:])
+ if bits not in [8, 16, 32]:
+ self.tile = [("bit", (0, 0)+self.size, offs,
+ (bits, 8, 3, 0, -1))]
+ return
+ except ValueError:
+ pass
+
+ if self.rawmode in ["RGB;T", "RYB;T"]:
+ # Old LabEye/3PC files. Would be very surprised if anyone
+ # ever stumbled upon such a file ;-)
+ size = self.size[0] * self.size[1]
+ self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
+ ("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
+ ("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
+ else:
+ # LabEye/IFUNC files
+ self.tile = [("raw", (0, 0)+self.size, offs,
+ (self.rawmode, 0, -1))]
+
+ def seek(self, frame):
+
+ if frame < 0 or frame >= self.info[FRAMES]:
+ raise EOFError("seek outside sequence")
+
+ if self.frame == frame:
+ return
+
+ self.frame = frame
+
+ if self.mode == "1":
+ bits = 1
+ else:
+ bits = 8 * len(self.mode)
+
+ size = ((self.size[0] * bits + 7) // 8) * self.size[1]
+ offs = self.__offset + frame * size
+
+ self.fp = self.__fp
+
+ self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
+
+ def tell(self):
+
+ return self.frame
+
+#
+# --------------------------------------------------------------------
+# Save IM files
+
+SAVE = {
+ # mode: (im type, raw mode)
+ "1": ("0 1", "1"),
+ "L": ("Greyscale", "L"),
+ "LA": ("LA", "LA;L"),
+ "P": ("Greyscale", "P"),
+ "PA": ("LA", "PA;L"),
+ "I": ("L 32S", "I;32S"),
+ "I;16": ("L 16", "I;16"),
+ "I;16L": ("L 16L", "I;16L"),
+ "I;16B": ("L 16B", "I;16B"),
+ "F": ("L 32F", "F;32F"),
+ "RGB": ("RGB", "RGB;L"),
+ "RGBA": ("RGBA", "RGBA;L"),
+ "RGBX": ("RGBX", "RGBX;L"),
+ "CMYK": ("CMYK", "CMYK;L"),
+ "YCbCr": ("YCC", "YCbCr;L")
+}
+
+
+def _save(im, fp, filename, check=0):
+
+ try:
+ type, rawmode = SAVE[im.mode]
+ except KeyError:
+ raise ValueError("Cannot save %s images as IM" % im.mode)
+
+ try:
+ frames = im.encoderinfo["frames"]
+ except KeyError:
+ frames = 1
+
+ if check:
+ return check
+
+ fp.write(("Image type: %s image\r\n" % type).encode('ascii'))
+ if filename:
+ fp.write(("Name: %s\r\n" % filename).encode('ascii'))
+ fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
+ fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii'))
+ if im.mode == "P":
+ fp.write(b"Lut: 1\r\n")
+ fp.write(b"\000" * (511-fp.tell()) + b"\032")
+ if im.mode == "P":
+ fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
+ ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
+
+#
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open("IM", ImImageFile)
+Image.register_save("IM", _save)
+
+Image.register_extension("IM", ".im")
diff --git a/lib/Python/Lib/PIL/Image.py b/lib/Python/Lib/PIL/Image.py
new file mode 100644
index 000000000..7cfa72e6f
--- /dev/null
+++ b/lib/Python/Lib/PIL/Image.py
@@ -0,0 +1,2483 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# the Image class wrapper
+#
+# partial release history:
+# 1995-09-09 fl Created
+# 1996-03-11 fl PIL release 0.0 (proof of concept)
+# 1996-04-30 fl PIL release 0.1b1
+# 1999-07-28 fl PIL release 1.0 final
+# 2000-06-07 fl PIL release 1.1
+# 2000-10-20 fl PIL release 1.1.1
+# 2001-05-07 fl PIL release 1.1.2
+# 2002-03-15 fl PIL release 1.1.3
+# 2003-05-10 fl PIL release 1.1.4
+# 2005-03-28 fl PIL release 1.1.5
+# 2006-12-02 fl PIL release 1.1.6
+# 2009-11-15 fl PIL release 1.1.7
+#
+# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1995-2009 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from __future__ import print_function
+
+from PIL import VERSION, PILLOW_VERSION, _plugins
+
+import warnings
+
+
+class DecompressionBombWarning(RuntimeWarning):
+ pass
+
+
+class _imaging_not_installed:
+ # module placeholder
+ def __getattr__(self, id):
+ raise ImportError("The _imaging C module is not installed")
+
+
+# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image
+MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 / 4 / 3)
+
+try:
+ # give Tk a chance to set up the environment, in case we're
+ # using an _imaging module linked against libtcl/libtk (use
+ # __import__ to hide this from naive packagers; we don't really
+ # depend on Tk unless ImageTk is used, and that module already
+ # imports Tkinter)
+ __import__("FixTk")
+except ImportError:
+ pass
+
+try:
+ # If the _imaging C module is not present, Pillow will not load.
+ # Note that other modules should not refer to _imaging directly;
+ # import Image and use the Image.core variable instead.
+ # Also note that Image.core is not a publicly documented interface,
+ # and should be considered private and subject to change.
+ from PIL import _imaging as core
+ if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
+ raise ImportError("The _imaging extension was built for another "
+ " version of Pillow or PIL")
+
+except ImportError as v:
+ core = _imaging_not_installed()
+ # Explanations for ways that we know we might have an import error
+ if str(v).startswith("Module use of python"):
+ # The _imaging C module is present, but not compiled for
+ # the right version (windows only). Print a warning, if
+ # possible.
+ warnings.warn(
+ "The _imaging extension was built for another version "
+ "of Python.",
+ RuntimeWarning
+ )
+ elif str(v).startswith("The _imaging extension"):
+ warnings.warn(str(v), RuntimeWarning)
+ elif "Symbol not found: _PyUnicodeUCS2_FromString" in str(v):
+ warnings.warn(
+ "The _imaging extension was built for Python with UCS2 support; "
+ "recompile PIL or build Python --without-wide-unicode. ",
+ RuntimeWarning
+ )
+ elif "Symbol not found: _PyUnicodeUCS4_FromString" in str(v):
+ warnings.warn(
+ "The _imaging extension was built for Python with UCS4 support; "
+ "recompile PIL or build Python --with-wide-unicode. ",
+ RuntimeWarning
+ )
+ # Fail here anyway. Don't let people run with a mostly broken Pillow.
+ # see docs/porting-pil-to-pillow.rst
+ raise
+
+try:
+ import builtins
+except ImportError:
+ import __builtin__
+ builtins = __builtin__
+
+from PIL import ImageMode
+from PIL._binary import i8
+from PIL._util import isPath
+from PIL._util import isStringType
+from PIL._util import deferred_error
+
+import os
+import sys
+import io
+import struct
+
+# type stuff
+import collections
+import numbers
+
+# works everywhere, win for pypy, not cpython
+USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
+try:
+ import cffi
+ HAS_CFFI = True
+except:
+ HAS_CFFI = False
+
+
+def isImageType(t):
+ """
+ Checks if an object is an image object.
+
+ .. warning::
+
+ This function is for internal use only.
+
+ :param t: object to check if it's an image
+ :returns: True if the object is an image
+ """
+ return hasattr(t, "im")
+
+#
+# Debug level
+
+DEBUG = 0
+
+#
+# Constants (also defined in _imagingmodule.c!)
+
+NONE = 0
+
+# transpose
+FLIP_LEFT_RIGHT = 0
+FLIP_TOP_BOTTOM = 1
+ROTATE_90 = 2
+ROTATE_180 = 3
+ROTATE_270 = 4
+TRANSPOSE = 5
+
+# transforms
+AFFINE = 0
+EXTENT = 1
+PERSPECTIVE = 2
+QUAD = 3
+MESH = 4
+
+# resampling filters
+NEAREST = NONE = 0
+LANCZOS = ANTIALIAS = 1
+BILINEAR = LINEAR = 2
+BICUBIC = CUBIC = 3
+
+# dithers
+NONE = 0
+NEAREST = 0
+ORDERED = 1 # Not yet implemented
+RASTERIZE = 2 # Not yet implemented
+FLOYDSTEINBERG = 3 # default
+
+# palettes/quantizers
+WEB = 0
+ADAPTIVE = 1
+
+MEDIANCUT = 0
+MAXCOVERAGE = 1
+FASTOCTREE = 2
+
+# categories
+NORMAL = 0
+SEQUENCE = 1
+CONTAINER = 2
+
+if hasattr(core, 'DEFAULT_STRATEGY'):
+ DEFAULT_STRATEGY = core.DEFAULT_STRATEGY
+ FILTERED = core.FILTERED
+ HUFFMAN_ONLY = core.HUFFMAN_ONLY
+ RLE = core.RLE
+ FIXED = core.FIXED
+
+
+# --------------------------------------------------------------------
+# Registries
+
+ID = []
+OPEN = {}
+MIME = {}
+SAVE = {}
+EXTENSION = {}
+
+# --------------------------------------------------------------------
+# Modes supported by this version
+
+_MODEINFO = {
+ # NOTE: this table will be removed in future versions. use
+ # getmode* functions or ImageMode descriptors instead.
+
+ # official modes
+ "1": ("L", "L", ("1",)),
+ "L": ("L", "L", ("L",)),
+ "I": ("L", "I", ("I",)),
+ "F": ("L", "F", ("F",)),
+ "P": ("RGB", "L", ("P",)),
+ "RGB": ("RGB", "L", ("R", "G", "B")),
+ "RGBX": ("RGB", "L", ("R", "G", "B", "X")),
+ "RGBA": ("RGB", "L", ("R", "G", "B", "A")),
+ "CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
+ "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
+ "LAB": ("RGB", "L", ("L", "A", "B")),
+ "HSV": ("RGB", "L", ("H", "S", "V")),
+
+ # Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and
+ # BGR;24. Use these modes only if you know exactly what you're
+ # doing...
+
+}
+
+if sys.byteorder == 'little':
+ _ENDIAN = '<'
+else:
+ _ENDIAN = '>'
+
+_MODE_CONV = {
+ # official modes
+ "1": ('|b1', None), # broken
+ "L": ('|u1', None),
+ "I": (_ENDIAN + 'i4', None),
+ "F": (_ENDIAN + 'f4', None),
+ "P": ('|u1', None),
+ "RGB": ('|u1', 3),
+ "RGBX": ('|u1', 4),
+ "RGBA": ('|u1', 4),
+ "CMYK": ('|u1', 4),
+ "YCbCr": ('|u1', 3),
+ "LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
+ # I;16 == I;16L, and I;32 == I;32L
+ "I;16": ('<u2', None),
+ "I;16B": ('>u2', None),
+ "I;16L": ('<u2', None),
+ "I;16S": ('<i2', None),
+ "I;16BS": ('>i2', None),
+ "I;16LS": ('<i2', None),
+ "I;32": ('<u4', None),
+ "I;32B": ('>u4', None),
+ "I;32L": ('<u4', None),
+ "I;32S": ('<i4', None),
+ "I;32BS": ('>i4', None),
+ "I;32LS": ('<i4', None),
+}
+
+
+def _conv_type_shape(im):
+ shape = im.size[1], im.size[0]
+ typ, extra = _MODE_CONV[im.mode]
+ if extra is None:
+ return shape, typ
+ else:
+ return shape+(extra,), typ
+
+
+MODES = sorted(_MODEINFO.keys())
+
+# raw modes that may be memory mapped. NOTE: if you change this, you
+# may have to modify the stride calculation in map.c too!
+_MAPMODES = ("L", "P", "RGBX", "RGBA", "CMYK", "I;16", "I;16L", "I;16B")
+
+
+def getmodebase(mode):
+ """
+ Gets the "base" mode for given mode. This function returns "L" for
+ images that contain grayscale data, and "RGB" for images that
+ contain color data.
+
+ :param mode: Input mode.
+ :returns: "L" or "RGB".
+ :exception KeyError: If the input mode was not a standard mode.
+ """
+ return ImageMode.getmode(mode).basemode
+
+
+def getmodetype(mode):
+ """
+ Gets the storage type mode. Given a mode, this function returns a
+ single-layer mode suitable for storing individual bands.
+
+ :param mode: Input mode.
+ :returns: "L", "I", or "F".
+ :exception KeyError: If the input mode was not a standard mode.
+ """
+ return ImageMode.getmode(mode).basetype
+
+
+def getmodebandnames(mode):
+ """
+ Gets a list of individual band names. Given a mode, this function returns
+ a tuple containing the names of individual bands (use
+ :py:method:`~PIL.Image.getmodetype` to get the mode used to store each
+ individual band.
+
+ :param mode: Input mode.
+ :returns: A tuple containing band names. The length of the tuple
+ gives the number of bands in an image of the given mode.
+ :exception KeyError: If the input mode was not a standard mode.
+ """
+ return ImageMode.getmode(mode).bands
+
+
+def getmodebands(mode):
+ """
+ Gets the number of individual bands for this mode.
+
+ :param mode: Input mode.
+ :returns: The number of bands in this mode.
+ :exception KeyError: If the input mode was not a standard mode.
+ """
+ return len(ImageMode.getmode(mode).bands)
+
+# --------------------------------------------------------------------
+# Helpers
+
+_initialized = 0
+
+
+def preinit():
+ "Explicitly load standard file format drivers."
+
+ global _initialized
+ if _initialized >= 1:
+ return
+
+ try:
+ from PIL import BmpImagePlugin
+ except ImportError:
+ pass
+ try:
+ from PIL import GifImagePlugin
+ except ImportError:
+ pass
+ try:
+ from PIL import JpegImagePlugin
+ except ImportError:
+ pass
+ try:
+ from PIL import PpmImagePlugin
+ except ImportError:
+ pass
+ try:
+ from PIL import PngImagePlugin
+ except ImportError:
+ pass
+# try:
+# import TiffImagePlugin
+# except ImportError:
+# pass
+
+ _initialized = 1
+
+
+def init():
+ """
+ Explicitly initializes the Python Imaging Library. This function
+ loads all available file format drivers.
+ """
+
+ global _initialized
+ if _initialized >= 2:
+ return 0
+
+ for plugin in _plugins:
+ try:
+ if DEBUG:
+ print ("Importing %s" % plugin)
+ __import__("PIL.%s" % plugin, globals(), locals(), [])
+ except ImportError:
+ if DEBUG:
+ print("Image: failed to import", end=' ')
+ print(plugin, ":", sys.exc_info()[1])
+
+ if OPEN or SAVE:
+ _initialized = 2
+ return 1
+
+
+# --------------------------------------------------------------------
+# Codec factories (used by tobytes/frombytes and ImageFile.load)
+
+def _getdecoder(mode, decoder_name, args, extra=()):
+
+ # tweak arguments
+ if args is None:
+ args = ()
+ elif not isinstance(args, tuple):
+ args = (args,)
+
+ try:
+ # get decoder
+ decoder = getattr(core, decoder_name + "_decoder")
+ # print(decoder, mode, args + extra)
+ return decoder(mode, *args + extra)
+ except AttributeError:
+ raise IOError("decoder %s not available" % decoder_name)
+
+
+def _getencoder(mode, encoder_name, args, extra=()):
+
+ # tweak arguments
+ if args is None:
+ args = ()
+ elif not isinstance(args, tuple):
+ args = (args,)
+
+ try:
+ # get encoder
+ encoder = getattr(core, encoder_name + "_encoder")
+ # print(encoder, mode, args + extra)
+ return encoder(mode, *args + extra)
+ except AttributeError:
+ raise IOError("encoder %s not available" % encoder_name)
+
+
+# --------------------------------------------------------------------
+# Simple expression analyzer
+
+def coerce_e(value):
+ return value if isinstance(value, _E) else _E(value)
+
+
+class _E:
+ def __init__(self, data):
+ self.data = data
+
+ def __add__(self, other):
+ return _E((self.data, "__add__", coerce_e(other).data))
+
+ def __mul__(self, other):
+ return _E((self.data, "__mul__", coerce_e(other).data))
+
+
+def _getscaleoffset(expr):
+ stub = ["stub"]
+ data = expr(_E(stub)).data
+ try:
+ (a, b, c) = data # simplified syntax
+ if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
+ return c, 0.0
+ if a is stub and b == "__add__" and isinstance(c, numbers.Number):
+ return 1.0, c
+ except TypeError:
+ pass
+ try:
+ ((a, b, c), d, e) = data # full syntax
+ if (a is stub and b == "__mul__" and isinstance(c, numbers.Number) and
+ d == "__add__" and isinstance(e, numbers.Number)):
+ return c, e
+ except TypeError:
+ pass
+ raise ValueError("illegal expression")
+
+
+# --------------------------------------------------------------------
+# Implementation wrapper
+
+class Image:
+ """
+ This class represents an image object. To create
+ :py:class:`~PIL.Image.Image` objects, use the appropriate factory
+ functions. There's hardly ever any reason to call the Image constructor
+ directly.
+
+ * :py:func:`~PIL.Image.open`
+ * :py:func:`~PIL.Image.new`
+ * :py:func:`~PIL.Image.frombytes`
+ """
+ format = None
+ format_description = None
+
+ def __init__(self):
+ # FIXME: take "new" parameters / other image?
+ # FIXME: turn mode and size into delegating properties?
+ self.im = None
+ self.mode = ""
+ self.size = (0, 0)
+ self.palette = None
+ self.info = {}
+ self.category = NORMAL
+ self.readonly = 0
+ self.pyaccess = None
+
+ def _new(self, im):
+ new = Image()
+ new.im = im
+ new.mode = im.mode
+ new.size = im.size
+ new.palette = self.palette
+ if im.mode == "P" and not new.palette:
+ from PIL import ImagePalette
+ new.palette = ImagePalette.ImagePalette()
+ try:
+ new.info = self.info.copy()
+ except AttributeError:
+ # fallback (pre-1.5.2)
+ new.info = {}
+ for k, v in self.info:
+ new.info[k] = v
+ return new
+
+ _makeself = _new # compatibility
+
+ # Context Manager Support
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
+ def close(self):
+ """
+ Closes the file pointer, if possible.
+
+ This operation will destroy the image core and release its memory.
+ The image data will be unusable afterward.
+
+ This function is only required to close images that have not
+ had their file read and closed by the
+ :py:meth:`~PIL.Image.Image.load` method.
+ """
+ try:
+ self.fp.close()
+ except Exception as msg:
+ if DEBUG:
+ print ("Error closing: %s" % msg)
+
+ # Instead of simply setting to None, we're setting up a
+ # deferred error that will better explain that the core image
+ # object is gone.
+ self.im = deferred_error(ValueError("Operation on closed image"))
+
+ def _copy(self):
+ self.load()
+ self.im = self.im.copy()
+ self.pyaccess = None
+ self.readonly = 0
+
+ def _dump(self, file=None, format=None):
+ import tempfile
+ suffix = ''
+ if format:
+ suffix = '.'+format
+ if not file:
+ f, file = tempfile.mkstemp(suffix)
+ os.close(f)
+
+ self.load()
+ if not format or format == "PPM":
+ self.im.save_ppm(file)
+ else:
+ if not file.endswith(format):
+ file = file + "." + format
+ self.save(file, format)
+ return file
+
+ def __eq__(self, other):
+ if self.__class__.__name__ != other.__class__.__name__:
+ return False
+ a = (self.mode == other.mode)
+ b = (self.size == other.size)
+ c = (self.getpalette() == other.getpalette())
+ d = (self.info == other.info)
+ e = (self.category == other.category)
+ f = (self.readonly == other.readonly)
+ g = (self.tobytes() == other.tobytes())
+ return a and b and c and d and e and f and g
+
+ def __ne__(self, other):
+ eq = (self == other)
+ return not eq
+
+ def __repr__(self):
+ return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
+ self.__class__.__module__, self.__class__.__name__,
+ self.mode, self.size[0], self.size[1],
+ id(self)
+ )
+
+ def _repr_png_(self):
+ """ iPython display hook support
+
+ :returns: png version of the image as bytes
+ """
+ from io import BytesIO
+ b = BytesIO()
+ self.save(b, 'PNG')
+ return b.getvalue()
+
+ def __getattr__(self, name):
+ if name == "__array_interface__":
+ # numpy array interface support
+ new = {}
+ shape, typestr = _conv_type_shape(self)
+ new['shape'] = shape
+ new['typestr'] = typestr
+ new['data'] = self.tobytes()
+ return new
+ raise AttributeError(name)
+
+ def __getstate__(self):
+ return [
+ self.info,
+ self.mode,
+ self.size,
+ self.getpalette(),
+ self.tobytes()]
+
+ def __setstate__(self, state):
+ Image.__init__(self)
+ self.tile = []
+ info, mode, size, palette, data = state
+ self.info = info
+ self.mode = mode
+ self.size = size
+ self.im = core.new(mode, size)
+ if mode in ("L", "P") and palette:
+ self.putpalette(palette)
+ self.frombytes(data)
+
+ def tobytes(self, encoder_name="raw", *args):
+ """
+ Return image as a bytes object
+
+ :param encoder_name: What encoder to use. The default is to
+ use the standard "raw" encoder.
+ :param args: Extra arguments to the encoder.
+ :rtype: A bytes object.
+ """
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ if encoder_name == "raw" and args == ():
+ args = self.mode
+
+ self.load()
+
+ # unpack data
+ e = _getencoder(self.mode, encoder_name, args)
+ e.setimage(self.im)
+
+ bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
+
+ data = []
+ while True:
+ l, s, d = e.encode(bufsize)
+ data.append(d)
+ if s:
+ break
+ if s < 0:
+ raise RuntimeError("encoder error %d in tobytes" % s)
+
+ return b"".join(data)
+
+ # Declare tostring as alias to tobytes
+ def tostring(self, *args, **kw):
+ """Deprecated alias to tobytes.
+
+ .. deprecated:: 2.0
+ """
+ warnings.warn(
+ 'tostring() is deprecated. Please call tobytes() instead.',
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.tobytes(*args, **kw)
+
+ def tobitmap(self, name="image"):
+ """
+ Returns the image converted to an X11 bitmap.
+
+ .. note:: This method only works for mode "1" images.
+
+ :param name: The name prefix to use for the bitmap variables.
+ :returns: A string containing an X11 bitmap.
+ :raises ValueError: If the mode is not "1"
+ """
+
+ self.load()
+ if self.mode != "1":
+ raise ValueError("not a bitmap")
+ data = self.tobytes("xbm")
+ return b"".join([
+ ("#define %s_width %d\n" % (name, self.size[0])).encode('ascii'),
+ ("#define %s_height %d\n" % (name, self.size[1])).encode('ascii'),
+ ("static char %s_bits[] = {\n" % name).encode('ascii'), data, b"};"
+ ])
+
+ def frombytes(self, data, decoder_name="raw", *args):
+ """
+ Loads this image with pixel data from a bytes object.
+
+ This method is similar to the :py:func:`~PIL.Image.frombytes` function,
+ but loads data into this image instead of creating a new image object.
+ """
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ # default format
+ if decoder_name == "raw" and args == ():
+ args = self.mode
+
+ # unpack data
+ d = _getdecoder(self.mode, decoder_name, args)
+ d.setimage(self.im)
+ s = d.decode(data)
+
+ if s[0] >= 0:
+ raise ValueError("not enough image data")
+ if s[1] != 0:
+ raise ValueError("cannot decode image data")
+
+ def fromstring(self, *args, **kw):
+ """Deprecated alias to frombytes.
+
+ .. deprecated:: 2.0
+ """
+ warnings.warn(
+ 'fromstring() is deprecated. Please call frombytes() instead.',
+ DeprecationWarning)
+ return self.frombytes(*args, **kw)
+
+ def load(self):
+ """
+ Allocates storage for the image and loads the pixel data. In
+ normal cases, you don't need to call this method, since the
+ Image class automatically loads an opened image when it is
+ accessed for the first time. This method will close the file
+ associated with the image.
+
+ :returns: An image access object.
+ :rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
+ """
+ if self.im and self.palette and self.palette.dirty:
+ # realize palette
+ self.im.putpalette(*self.palette.getdata())
+ self.palette.dirty = 0
+ self.palette.mode = "RGB"
+ self.palette.rawmode = None
+ if "transparency" in self.info:
+ if isinstance(self.info["transparency"], int):
+ self.im.putpalettealpha(self.info["transparency"], 0)
+ else:
+ self.im.putpalettealphas(self.info["transparency"])
+ self.palette.mode = "RGBA"
+
+ if self.im:
+ if HAS_CFFI and USE_CFFI_ACCESS:
+ if self.pyaccess:
+ return self.pyaccess
+ from PIL import PyAccess
+ self.pyaccess = PyAccess.new(self, self.readonly)
+ if self.pyaccess:
+ return self.pyaccess
+ return self.im.pixel_access(self.readonly)
+
+ def verify(self):
+ """
+ Verifies the contents of a file. For data read from a file, this
+ method attempts to determine if the file is broken, without
+ actually decoding the image data. If this method finds any
+ problems, it raises suitable exceptions. If you need to load
+ the image after using this method, you must reopen the image
+ file.
+ """
+ pass
+
+ def convert(self, mode=None, matrix=None, dither=None,
+ palette=WEB, colors=256):
+ """
+ Returns a converted copy of this image. For the "P" mode, this
+ method translates pixels through the palette. If mode is
+ omitted, a mode is chosen so that all information in the image
+ and the palette can be represented without a palette.
+
+ The current version supports all possible conversions between
+ "L", "RGB" and "CMYK." The **matrix** argument only supports "L"
+ and "RGB".
+
+ When translating a color image to black and white (mode "L"),
+ the library uses the ITU-R 601-2 luma transform::
+
+ L = R * 299/1000 + G * 587/1000 + B * 114/1000
+
+ The default method of converting a greyscale ("L") or "RGB"
+ image into a bilevel (mode "1") image uses Floyd-Steinberg
+ dither to approximate the original image luminosity levels. If
+ dither is NONE, all non-zero values are set to 255 (white). To
+ use other thresholds, use the :py:meth:`~PIL.Image.Image.point`
+ method.
+
+ :param mode: The requested mode. See: :ref:`concept-modes`.
+ :param matrix: An optional conversion matrix. If given, this
+ should be 4- or 16-tuple containing floating point values.
+ :param dither: Dithering method, used when converting from
+ mode "RGB" to "P" or from "RGB" or "L" to "1".
+ Available methods are NONE or FLOYDSTEINBERG (default).
+ :param palette: Palette to use when converting from mode "RGB"
+ to "P". Available palettes are WEB or ADAPTIVE.
+ :param colors: Number of colors to use for the ADAPTIVE palette.
+ Defaults to 256.
+ :rtype: :py:class:`~PIL.Image.Image`
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ if not mode:
+ # determine default mode
+ if self.mode == "P":
+ self.load()
+ if self.palette:
+ mode = self.palette.mode
+ else:
+ mode = "RGB"
+ else:
+ return self.copy()
+
+ self.load()
+
+ if matrix:
+ # matrix conversion
+ if mode not in ("L", "RGB"):
+ raise ValueError("illegal conversion")
+ im = self.im.convert_matrix(mode, matrix)
+ return self._new(im)
+
+ if mode == "P" and self.mode == "RGBA":
+ return self.quantize(colors)
+
+ trns = None
+ delete_trns = False
+ # transparency handling
+ if "transparency" in self.info and \
+ self.info['transparency'] is not None:
+ if self.mode in ('L', 'RGB') and mode == 'RGBA':
+ # Use transparent conversion to promote from transparent
+ # color to an alpha channel.
+ return self._new(self.im.convert_transparent(
+ mode, self.info['transparency']))
+ elif self.mode in ('L', 'RGB', 'P') and mode in ('L', 'RGB', 'P'):
+ t = self.info['transparency']
+ if isinstance(t, bytes):
+ # Dragons. This can't be represented by a single color
+ warnings.warn('Palette images with Transparency ' +
+ ' expressed in bytes should be converted ' +
+ 'to RGBA images')
+ delete_trns = True
+ else:
+ # get the new transparency color.
+ # use existing conversions
+ trns_im = Image()._new(core.new(self.mode, (1, 1)))
+ if self.mode == 'P':
+ trns_im.putpalette(self.palette)
+ trns_im.putpixel((0, 0), t)
+
+ if mode in ('L', 'RGB'):
+ trns_im = trns_im.convert(mode)
+ else:
+ # can't just retrieve the palette number, got to do it
+ # after quantization.
+ trns_im = trns_im.convert('RGB')
+ trns = trns_im.getpixel((0, 0))
+
+ elif self.mode == 'P' and mode == 'RGBA':
+ t = self.info['transparency']
+ delete_trns = True
+
+ if isinstance(t, bytes):
+ self.im.putpalettealphas(t)
+ elif isinstance(t, int):
+ self.im.putpalettealpha(t, 0)
+ else:
+ raise ValueError("Transparency for P mode should" +
+ " be bytes or int")
+
+ if mode == "P" and palette == ADAPTIVE:
+ im = self.im.quantize(colors)
+ new = self._new(im)
+ from PIL import ImagePalette
+ new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB"))
+ if delete_trns:
+ # This could possibly happen if we requantize to fewer colors.
+ # The transparency would be totally off in that case.
+ del(new.info['transparency'])
+ if trns is not None:
+ try:
+ new.info['transparency'] = new.palette.getcolor(trns)
+ except:
+ # if we can't make a transparent color, don't leave the old
+ # transparency hanging around to mess us up.
+ del(new.info['transparency'])
+ warnings.warn("Couldn't allocate palette entry " +
+ "for transparency")
+ return new
+
+ # colorspace conversion
+ if dither is None:
+ dither = FLOYDSTEINBERG
+
+ try:
+ im = self.im.convert(mode, dither)
+ except ValueError:
+ try:
+ # normalize source image and try again
+ im = self.im.convert(getmodebase(self.mode))
+ im = im.convert(mode, dither)
+ except KeyError:
+ raise ValueError("illegal conversion")
+
+ new_im = self._new(im)
+ if delete_trns:
+ # crash fail if we leave a bytes transparency in an rgb/l mode.
+ del(new_im.info['transparency'])
+ if trns is not None:
+ if new_im.mode == 'P':
+ try:
+ new_im.info['transparency'] = new_im.palette.getcolor(trns)
+ except:
+ del(new_im.info['transparency'])
+ warnings.warn("Couldn't allocate palette entry " +
+ "for transparency")
+ else:
+ new_im.info['transparency'] = trns
+ return new_im
+
+ def quantize(self, colors=256, method=None, kmeans=0, palette=None):
+ """
+ Convert the image to 'P' mode with the specified number
+ of colors.
+
+ :param colors: The desired number of colors, <= 256
+ :param method: 0 = median cut
+ 1 = maximum coverage
+ 2 = fast octree
+ :param kmeans: Integer
+ :param palette: Quantize to the :py:class:`PIL.ImagingPalette` palette.
+ :returns: A new image
+
+ """
+
+ self.load()
+
+ if method is None:
+ # defaults:
+ method = 0
+ if self.mode == 'RGBA':
+ method = 2
+
+ if self.mode == 'RGBA' and method != 2:
+ # Caller specified an invalid mode.
+ raise ValueError('Fast Octree (method == 2) is the ' +
+ ' only valid method for quantizing RGBA images')
+
+ if palette:
+ # use palette from reference image
+ palette.load()
+ if palette.mode != "P":
+ raise ValueError("bad mode for palette image")
+ if self.mode != "RGB" and self.mode != "L":
+ raise ValueError(
+ "only RGB or L mode images can be quantized to a palette"
+ )
+ im = self.im.convert("P", 1, palette.im)
+ return self._makeself(im)
+
+ im = self.im.quantize(colors, method, kmeans)
+ return self._new(im)
+
+ def copy(self):
+ """
+ Copies this image. Use this method if you wish to paste things
+ into an image, but still retain the original.
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+ self.load()
+ im = self.im.copy()
+ return self._new(im)
+
+ def crop(self, box=None):
+ """
+ Returns a rectangular region from this image. The box is a
+ 4-tuple defining the left, upper, right, and lower pixel
+ coordinate.
+
+ This is a lazy operation. Changes to the source image may or
+ may not be reflected in the cropped image. To break the
+ connection, call the :py:meth:`~PIL.Image.Image.load` method on
+ the cropped copy.
+
+ :param box: The crop rectangle, as a (left, upper, right, lower)-tuple.
+ :rtype: :py:class:`~PIL.Image.Image`
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ self.load()
+ if box is None:
+ return self.copy()
+
+ # lazy operation
+ return _ImageCrop(self, box)
+
+ def draft(self, mode, size):
+ """
+ Configures the image file loader so it returns a version of the
+ image that as closely as possible matches the given mode and
+ size. For example, you can use this method to convert a color
+ JPEG to greyscale while loading it, or to extract a 128x192
+ version from a PCD file.
+
+ Note that this method modifies the :py:class:`~PIL.Image.Image` object
+ in place. If the image has already been loaded, this method has no
+ effect.
+
+ :param mode: The requested mode.
+ :param size: The requested size.
+ """
+ pass
+
+ def _expand(self, xmargin, ymargin=None):
+ if ymargin is None:
+ ymargin = xmargin
+ self.load()
+ return self._new(self.im.expand(xmargin, ymargin, 0))
+
+ def filter(self, filter):
+ """
+ Filters this image using the given filter. For a list of
+ available filters, see the :py:mod:`~PIL.ImageFilter` module.
+
+ :param filter: Filter kernel.
+ :returns: An :py:class:`~PIL.Image.Image` object. """
+
+ self.load()
+
+ if isinstance(filter, collections.Callable):
+ filter = filter()
+ if not hasattr(filter, "filter"):
+ raise TypeError("filter argument should be ImageFilter.Filter " +
+ "instance or class")
+
+ if self.im.bands == 1:
+ return self._new(filter.filter(self.im))
+ # fix to handle multiband images since _imaging doesn't
+ ims = []
+ for c in range(self.im.bands):
+ ims.append(self._new(filter.filter(self.im.getband(c))))
+ return merge(self.mode, ims)
+
+ def getbands(self):
+ """
+ Returns a tuple containing the name of each band in this image.
+ For example, **getbands** on an RGB image returns ("R", "G", "B").
+
+ :returns: A tuple containing band names.
+ :rtype: tuple
+ """
+ return ImageMode.getmode(self.mode).bands
+
+ def getbbox(self):
+ """
+ Calculates the bounding box of the non-zero regions in the
+ image.
+
+ :returns: The bounding box is returned as a 4-tuple defining the
+ left, upper, right, and lower pixel coordinate. If the image
+ is completely empty, this method returns None.
+
+ """
+
+ self.load()
+ return self.im.getbbox()
+
+ def getcolors(self, maxcolors=256):
+ """
+ Returns a list of colors used in this image.
+
+ :param maxcolors: Maximum number of colors. If this number is
+ exceeded, this method returns None. The default limit is
+ 256 colors.
+ :returns: An unsorted list of (count, pixel) values.
+ """
+
+ self.load()
+ if self.mode in ("1", "L", "P"):
+ h = self.im.histogram()
+ out = []
+ for i in range(256):
+ if h[i]:
+ out.append((h[i], i))
+ if len(out) > maxcolors:
+ return None
+ return out
+ return self.im.getcolors(maxcolors)
+
+ def getdata(self, band=None):
+ """
+ Returns the contents of this image as a sequence object
+ containing pixel values. The sequence object is flattened, so
+ that values for line one follow directly after the values of
+ line zero, and so on.
+
+ Note that the sequence object returned by this method is an
+ internal PIL data type, which only supports certain sequence
+ operations. To convert it to an ordinary sequence (e.g. for
+ printing), use **list(im.getdata())**.
+
+ :param band: What band to return. The default is to return
+ all bands. To return a single band, pass in the index
+ value (e.g. 0 to get the "R" band from an "RGB" image).
+ :returns: A sequence-like object.
+ """
+
+ self.load()
+ if band is not None:
+ return self.im.getband(band)
+ return self.im # could be abused
+
+ def getextrema(self):
+ """
+ Gets the the minimum and maximum pixel values for each band in
+ the image.
+
+ :returns: For a single-band image, a 2-tuple containing the
+ minimum and maximum pixel value. For a multi-band image,
+ a tuple containing one 2-tuple for each band.
+ """
+
+ self.load()
+ if self.im.bands > 1:
+ extrema = []
+ for i in range(self.im.bands):
+ extrema.append(self.im.getband(i).getextrema())
+ return tuple(extrema)
+ return self.im.getextrema()
+
+ def getim(self):
+ """
+ Returns a capsule that points to the internal image memory.
+
+ :returns: A capsule object.
+ """
+
+ self.load()
+ return self.im.ptr
+
+ def getpalette(self):
+ """
+ Returns the image palette as a list.
+
+ :returns: A list of color values [r, g, b, ...], or None if the
+ image has no palette.
+ """
+
+ self.load()
+ try:
+ if bytes is str:
+ return [i8(c) for c in self.im.getpalette()]
+ else:
+ return list(self.im.getpalette())
+ except ValueError:
+ return None # no palette
+
+ def getpixel(self, xy):
+ """
+ Returns the pixel value at a given position.
+
+ :param xy: The coordinate, given as (x, y).
+ :returns: The pixel value. If the image is a multi-layer image,
+ this method returns a tuple.
+ """
+
+ self.load()
+ if self.pyaccess:
+ return self.pyaccess.getpixel(xy)
+ return self.im.getpixel(xy)
+
+ def getprojection(self):
+ """
+ Get projection to x and y axes
+
+ :returns: Two sequences, indicating where there are non-zero
+ pixels along the X-axis and the Y-axis, respectively.
+ """
+
+ self.load()
+ x, y = self.im.getprojection()
+ return [i8(c) for c in x], [i8(c) for c in y]
+
+ def histogram(self, mask=None, extrema=None):
+ """
+ Returns a histogram for the image. The histogram is returned as
+ a list of pixel counts, one for each pixel value in the source
+ image. If the image has more than one band, the histograms for
+ all bands are concatenated (for example, the histogram for an
+ "RGB" image contains 768 values).
+
+ A bilevel image (mode "1") is treated as a greyscale ("L") image
+ by this method.
+
+ If a mask is provided, the method returns a histogram for those
+ parts of the image where the mask image is non-zero. The mask
+ image must have the same size as the image, and be either a
+ bi-level image (mode "1") or a greyscale image ("L").
+
+ :param mask: An optional mask.
+ :returns: A list containing pixel counts.
+ """
+ self.load()
+ if mask:
+ mask.load()
+ return self.im.histogram((0, 0), mask.im)
+ if self.mode in ("I", "F"):
+ if extrema is None:
+ extrema = self.getextrema()
+ return self.im.histogram(extrema)
+ return self.im.histogram()
+
+ def offset(self, xoffset, yoffset=None):
+ """
+ .. deprecated:: 2.0
+
+ .. note:: New code should use :py:func:`PIL.ImageChops.offset`.
+
+ Returns a copy of the image where the data has been offset by the given
+ distances. Data wraps around the edges. If **yoffset** is omitted, it
+ is assumed to be equal to **xoffset**.
+
+ :param xoffset: The horizontal distance.
+ :param yoffset: The vertical distance. If omitted, both
+ distances are set to the same value.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+ if warnings:
+ warnings.warn(
+ "'offset' is deprecated; use 'ImageChops.offset' instead",
+ DeprecationWarning, stacklevel=2
+ )
+ from PIL import ImageChops
+ return ImageChops.offset(self, xoffset, yoffset)
+
+ def paste(self, im, box=None, mask=None):
+ """
+ Pastes another image into this image. The box argument is either
+ a 2-tuple giving the upper left corner, a 4-tuple defining the
+ left, upper, right, and lower pixel coordinate, or None (same as
+ (0, 0)). If a 4-tuple is given, the size of the pasted image
+ must match the size of the region.
+
+ If the modes don't match, the pasted image is converted to the mode of
+ this image (see the :py:meth:`~PIL.Image.Image.convert` method for
+ details).
+
+ Instead of an image, the source can be a integer or tuple
+ containing pixel values. The method then fills the region
+ with the given color. When creating RGB images, you can
+ also use color strings as supported by the ImageColor module.
+
+ If a mask is given, this method updates only the regions
+ indicated by the mask. You can use either "1", "L" or "RGBA"
+ images (in the latter case, the alpha band is used as mask).
+ Where the mask is 255, the given image is copied as is. Where
+ the mask is 0, the current value is preserved. Intermediate
+ values can be used for transparency effects.
+
+ Note that if you paste an "RGBA" image, the alpha band is
+ ignored. You can work around this by using the same image as
+ both source image and mask.
+
+ :param im: Source image or pixel value (integer or tuple).
+ :param box: An optional 4-tuple giving the region to paste into.
+ If a 2-tuple is used instead, it's treated as the upper left
+ corner. If omitted or None, the source is pasted into the
+ upper left corner.
+
+ If an image is given as the second argument and there is no
+ third, the box defaults to (0, 0), and the second argument
+ is interpreted as a mask image.
+ :param mask: An optional mask image.
+ """
+
+ if isImageType(box) and mask is None:
+ # abbreviated paste(im, mask) syntax
+ mask = box
+ box = None
+
+ if box is None:
+ # cover all of self
+ box = (0, 0) + self.size
+
+ if len(box) == 2:
+ # lower left corner given; get size from image or mask
+ if isImageType(im):
+ size = im.size
+ elif isImageType(mask):
+ size = mask.size
+ else:
+ # FIXME: use self.size here?
+ raise ValueError(
+ "cannot determine region size; use 4-item box"
+ )
+ box = box + (box[0]+size[0], box[1]+size[1])
+
+ if isStringType(im):
+ from PIL import ImageColor
+ im = ImageColor.getcolor(im, self.mode)
+
+ elif isImageType(im):
+ im.load()
+ if self.mode != im.mode:
+ if self.mode != "RGB" or im.mode not in ("RGBA", "RGBa"):
+ # should use an adapter for this!
+ im = im.convert(self.mode)
+ im = im.im
+
+ self.load()
+ if self.readonly:
+ self._copy()
+
+ if mask:
+ mask.load()
+ self.im.paste(im, box, mask.im)
+ else:
+ self.im.paste(im, box)
+
+ def point(self, lut, mode=None):
+ """
+ Maps this image through a lookup table or function.
+
+ :param lut: A lookup table, containing 256 (or 65336 if
+ self.mode=="I" and mode == "L") values per band in the
+ image. A function can be used instead, it should take a
+ single argument. The function is called once for each
+ possible pixel value, and the resulting table is applied to
+ all bands of the image.
+ :param mode: Output mode (default is same as input). In the
+ current version, this can only be used if the source image
+ has mode "L" or "P", and the output has mode "1" or the
+ source image mode is "I" and the output mode is "L".
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ self.load()
+
+ if isinstance(lut, ImagePointHandler):
+ return lut.point(self)
+
+ if callable(lut):
+ # if it isn't a list, it should be a function
+ if self.mode in ("I", "I;16", "F"):
+ # check if the function can be used with point_transform
+ # UNDONE wiredfool -- I think this prevents us from ever doing
+ # a gamma function point transform on > 8bit images.
+ scale, offset = _getscaleoffset(lut)
+ return self._new(self.im.point_transform(scale, offset))
+ # for other modes, convert the function to a table
+ lut = [lut(i) for i in range(256)] * self.im.bands
+
+ if self.mode == "F":
+ # FIXME: _imaging returns a confusing error message for this case
+ raise ValueError("point operation not supported for this mode")
+
+ return self._new(self.im.point(lut, mode))
+
+ def putalpha(self, alpha):
+ """
+ Adds or replaces the alpha layer in this image. If the image
+ does not have an alpha layer, it's converted to "LA" or "RGBA".
+ The new layer must be either "L" or "1".
+
+ :param alpha: The new alpha layer. This can either be an "L" or "1"
+ image having the same size as this image, or an integer or
+ other color value.
+ """
+
+ self.load()
+ if self.readonly:
+ self._copy()
+
+ if self.mode not in ("LA", "RGBA"):
+ # attempt to promote self to a matching alpha mode
+ try:
+ mode = getmodebase(self.mode) + "A"
+ try:
+ self.im.setmode(mode)
+ self.pyaccess = None
+ except (AttributeError, ValueError):
+ # do things the hard way
+ im = self.im.convert(mode)
+ if im.mode not in ("LA", "RGBA"):
+ raise ValueError # sanity check
+ self.im = im
+ self.pyaccess = None
+ self.mode = self.im.mode
+ except (KeyError, ValueError):
+ raise ValueError("illegal image mode")
+
+ if self.mode == "LA":
+ band = 1
+ else:
+ band = 3
+
+ if isImageType(alpha):
+ # alpha layer
+ if alpha.mode not in ("1", "L"):
+ raise ValueError("illegal image mode")
+ alpha.load()
+ if alpha.mode == "1":
+ alpha = alpha.convert("L")
+ else:
+ # constant alpha
+ try:
+ self.im.fillband(band, alpha)
+ except (AttributeError, ValueError):
+ # do things the hard way
+ alpha = new("L", self.size, alpha)
+ else:
+ return
+
+ self.im.putband(alpha.im, band)
+
+ def putdata(self, data, scale=1.0, offset=0.0):
+ """
+ Copies pixel data to this image. This method copies data from a
+ sequence object into the image, starting at the upper left
+ corner (0, 0), and continuing until either the image or the
+ sequence ends. The scale and offset values are used to adjust
+ the sequence values: **pixel = value*scale + offset**.
+
+ :param data: A sequence object.
+ :param scale: An optional scale value. The default is 1.0.
+ :param offset: An optional offset value. The default is 0.0.
+ """
+
+ self.load()
+ if self.readonly:
+ self._copy()
+
+ self.im.putdata(data, scale, offset)
+
+ def putpalette(self, data, rawmode="RGB"):
+ """
+ Attaches a palette to this image. The image must be a "P" or
+ "L" image, and the palette sequence must contain 768 integer
+ values, where each group of three values represent the red,
+ green, and blue values for the corresponding pixel
+ index. Instead of an integer sequence, you can use an 8-bit
+ string.
+
+ :param data: A palette sequence (either a list or a string).
+ """
+ from PIL import ImagePalette
+
+ if self.mode not in ("L", "P"):
+ raise ValueError("illegal image mode")
+ self.load()
+ if isinstance(data, ImagePalette.ImagePalette):
+ palette = ImagePalette.raw(data.rawmode, data.palette)
+ else:
+ if not isinstance(data, bytes):
+ if bytes is str:
+ data = "".join(chr(x) for x in data)
+ else:
+ data = bytes(data)
+ palette = ImagePalette.raw(rawmode, data)
+ self.mode = "P"
+ self.palette = palette
+ self.palette.mode = "RGB"
+ self.load() # install new palette
+
+ def putpixel(self, xy, value):
+ """
+ Modifies the pixel at the given position. The color is given as
+ a single numerical value for single-band images, and a tuple for
+ multi-band images.
+
+ Note that this method is relatively slow. For more extensive changes,
+ use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
+ module instead.
+
+ See:
+
+ * :py:meth:`~PIL.Image.Image.paste`
+ * :py:meth:`~PIL.Image.Image.putdata`
+ * :py:mod:`~PIL.ImageDraw`
+
+ :param xy: The pixel coordinate, given as (x, y).
+ :param value: The pixel value.
+ """
+
+ self.load()
+ if self.readonly:
+ self._copy()
+ self.pyaccess = None
+ self.load()
+
+ if self.pyaccess:
+ return self.pyaccess.putpixel(xy, value)
+ return self.im.putpixel(xy, value)
+
+ def resize(self, size, resample=NEAREST):
+ """
+ Returns a resized copy of this image.
+
+ :param size: The requested size in pixels, as a 2-tuple:
+ (width, height).
+ :param resample: An optional resampling filter. This can be
+ one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
+ :py:attr:`PIL.Image.BILINEAR` (linear interpolation),
+ :py:attr:`PIL.Image.BICUBIC` (cubic spline interpolation), or
+ :py:attr:`PIL.Image.LANCZOS` (a high-quality downsampling filter).
+ If omitted, or if the image has mode "1" or "P", it is
+ set :py:attr:`PIL.Image.NEAREST`.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS):
+ raise ValueError("unknown resampling filter")
+
+ self.load()
+
+ size = tuple(size)
+ if self.size == size:
+ return self._new(self.im)
+
+ if self.mode in ("1", "P"):
+ resample = NEAREST
+
+ if self.mode == 'RGBA':
+ return self.convert('RGBa').resize(size, resample).convert('RGBA')
+
+ return self._new(self.im.resize(size, resample))
+
+ def rotate(self, angle, resample=NEAREST, expand=0):
+ """
+ Returns a rotated copy of this image. This method returns a
+ copy of this image, rotated the given number of degrees counter
+ clockwise around its centre.
+
+ :param angle: In degrees counter clockwise.
+ :param resample: An optional resampling filter. This can be
+ one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
+ :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
+ environment), or :py:attr:`PIL.Image.BICUBIC`
+ (cubic spline interpolation in a 4x4 environment).
+ If omitted, or if the image has mode "1" or "P", it is
+ set :py:attr:`PIL.Image.NEAREST`.
+ :param expand: Optional expansion flag. If true, expands the output
+ image to make it large enough to hold the entire rotated image.
+ If false or omitted, make the output image the same size as the
+ input image.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ if expand:
+ import math
+ angle = -angle * math.pi / 180
+ matrix = [
+ math.cos(angle), math.sin(angle), 0.0,
+ -math.sin(angle), math.cos(angle), 0.0
+ ]
+
+ def transform(x, y, matrix=matrix):
+ (a, b, c, d, e, f) = matrix
+ return a*x + b*y + c, d*x + e*y + f
+
+ # calculate output size
+ w, h = self.size
+ xx = []
+ yy = []
+ for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
+ x, y = transform(x, y)
+ xx.append(x)
+ yy.append(y)
+ w = int(math.ceil(max(xx)) - math.floor(min(xx)))
+ h = int(math.ceil(max(yy)) - math.floor(min(yy)))
+
+ # adjust center
+ x, y = transform(w / 2.0, h / 2.0)
+ matrix[2] = self.size[0] / 2.0 - x
+ matrix[5] = self.size[1] / 2.0 - y
+
+ return self.transform((w, h), AFFINE, matrix, resample)
+
+ if resample not in (NEAREST, BILINEAR, BICUBIC):
+ raise ValueError("unknown resampling filter")
+
+ self.load()
+
+ if self.mode in ("1", "P"):
+ resample = NEAREST
+
+ return self._new(self.im.rotate(angle, resample))
+
+ def save(self, fp, format=None, **params):
+ """
+ Saves this image under the given filename. If no format is
+ specified, the format to use is determined from the filename
+ extension, if possible.
+
+ Keyword options can be used to provide additional instructions
+ to the writer. If a writer doesn't recognise an option, it is
+ silently ignored. The available options are described in the
+ :doc:`image format documentation
+ <../handbook/image-file-formats>` for each writer.
+
+ You can use a file object instead of a filename. In this case,
+ you must always specify the format. The file object must
+ implement the ``seek``, ``tell``, and ``write``
+ methods, and be opened in binary mode.
+
+ :param fp: File name or file object.
+ :param format: Optional format override. If omitted, the
+ format to use is determined from the filename extension.
+ If a file object was used instead of a filename, this
+ parameter should always be used.
+ :param options: Extra parameters to the image writer.
+ :returns: None
+ :exception KeyError: If the output format could not be determined
+ from the file name. Use the format option to solve this.
+ :exception IOError: If the file could not be written. The file
+ may have been created, and may contain partial data.
+ """
+
+ if isPath(fp):
+ filename = fp
+ else:
+ if hasattr(fp, "name") and isPath(fp.name):
+ filename = fp.name
+ else:
+ filename = ""
+
+ # may mutate self!
+ self.load()
+
+ self.encoderinfo = params
+ self.encoderconfig = ()
+
+ preinit()
+
+ ext = os.path.splitext(filename)[1].lower()
+
+ if not format:
+ try:
+ format = EXTENSION[ext]
+ except KeyError:
+ init()
+ try:
+ format = EXTENSION[ext]
+ except KeyError:
+ raise KeyError(ext) # unknown extension
+
+ try:
+ save_handler = SAVE[format.upper()]
+ except KeyError:
+ init()
+ save_handler = SAVE[format.upper()] # unknown format
+
+ if isPath(fp):
+ fp = builtins.open(fp, "wb")
+ close = 1
+ else:
+ close = 0
+
+ try:
+ save_handler(self, fp, filename)
+ finally:
+ # do what we can to clean up
+ if close:
+ fp.close()
+
+ def seek(self, frame):
+ """
+ Seeks to the given frame in this sequence file. If you seek
+ beyond the end of the sequence, the method raises an
+ **EOFError** exception. When a sequence file is opened, the
+ library automatically seeks to frame 0.
+
+ Note that in the current version of the library, most sequence
+ formats only allows you to seek to the next frame.
+
+ See :py:meth:`~PIL.Image.Image.tell`.
+
+ :param frame: Frame number, starting at 0.
+ :exception EOFError: If the call attempts to seek beyond the end
+ of the sequence.
+ """
+
+ # overridden by file handlers
+ if frame != 0:
+ raise EOFError
+
+ def show(self, title=None, command=None):
+ """
+ Displays this image. This method is mainly intended for
+ debugging purposes.
+
+ On Unix platforms, this method saves the image to a temporary
+ PPM file, and calls the **xv** utility.
+
+ On Windows, it saves the image to a temporary BMP file, and uses
+ the standard BMP display utility to show it (usually Paint).
+
+ :param title: Optional title to use for the image window,
+ where possible.
+ :param command: command used to show the image
+ """
+
+ _show(self, title=title, command=command)
+
+ def split(self):
+ """
+ Split this image into individual bands. This method returns a
+ tuple of individual image bands from an image. For example,
+ splitting an "RGB" image creates three new images each
+ containing a copy of one of the original bands (red, green,
+ blue).
+
+ :returns: A tuple containing bands.
+ """
+
+ self.load()
+ if self.im.bands == 1:
+ ims = [self.copy()]
+ else:
+ ims = []
+ for i in range(self.im.bands):
+ ims.append(self._new(self.im.getband(i)))
+ return tuple(ims)
+
+ def tell(self):
+ """
+ Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`.
+
+ :returns: Frame number, starting with 0.
+ """
+ return 0
+
+ def thumbnail(self, size, resample=BICUBIC):
+ """
+ Make this image into a thumbnail. This method modifies the
+ image to contain a thumbnail version of itself, no larger than
+ the given size. This method calculates an appropriate thumbnail
+ size to preserve the aspect of the image, calls the
+ :py:meth:`~PIL.Image.Image.draft` method to configure the file reader
+ (where applicable), and finally resizes the image.
+
+ Note that this function modifies the :py:class:`~PIL.Image.Image`
+ object in place. If you need to use the full resolution image as well,
+ apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
+ image.
+
+ :param size: Requested size.
+ :param resample: Optional resampling filter. This can be one
+ of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
+ :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`.
+ If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`.
+ (was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0)
+ :returns: None
+ """
+
+ # preserve aspect ratio
+ x, y = self.size
+ if x > size[0]:
+ y = int(max(y * size[0] / x, 1))
+ x = int(size[0])
+ if y > size[1]:
+ x = int(max(x * size[1] / y, 1))
+ y = int(size[1])
+ size = x, y
+
+ if size == self.size:
+ return
+
+ self.draft(None, size)
+
+ im = self.resize(size, resample)
+
+ self.im = im.im
+ self.mode = im.mode
+ self.size = size
+
+ self.readonly = 0
+ self.pyaccess = None
+
+ # FIXME: the different tranform methods need further explanation
+ # instead of bloating the method docs, add a separate chapter.
+ def transform(self, size, method, data=None, resample=NEAREST, fill=1):
+ """
+ Transforms this image. This method creates a new image with the
+ given size, and the same mode as the original, and copies data
+ to the new image using the given transform.
+
+ :param size: The output size.
+ :param method: The transformation method. This is one of
+ :py:attr:`PIL.Image.EXTENT` (cut out a rectangular subregion),
+ :py:attr:`PIL.Image.AFFINE` (affine transform),
+ :py:attr:`PIL.Image.PERSPECTIVE` (perspective transform),
+ :py:attr:`PIL.Image.QUAD` (map a quadrilateral to a rectangle), or
+ :py:attr:`PIL.Image.MESH` (map a number of source quadrilaterals
+ in one operation).
+ :param data: Extra data to the transformation method.
+ :param resample: Optional resampling filter. It can be one of
+ :py:attr:`PIL.Image.NEAREST` (use nearest neighbour),
+ :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
+ environment), or :py:attr:`PIL.Image.BICUBIC` (cubic spline
+ interpolation in a 4x4 environment). If omitted, or if the image
+ has mode "1" or "P", it is set to :py:attr:`PIL.Image.NEAREST`.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ if self.mode == 'RGBA':
+ return self.convert('RGBa').transform(
+ size, method, data, resample, fill).convert('RGBA')
+
+ if isinstance(method, ImageTransformHandler):
+ return method.transform(size, self, resample=resample, fill=fill)
+ if hasattr(method, "getdata"):
+ # compatibility w. old-style transform objects
+ method, data = method.getdata()
+ if data is None:
+ raise ValueError("missing method data")
+
+ im = new(self.mode, size, None)
+ if method == MESH:
+ # list of quads
+ for box, quad in data:
+ im.__transformer(box, self, QUAD, quad, resample, fill)
+ else:
+ im.__transformer((0, 0)+size, self, method, data, resample, fill)
+
+ return im
+
+ def __transformer(self, box, image, method, data,
+ resample=NEAREST, fill=1):
+
+ # FIXME: this should be turned into a lazy operation (?)
+
+ w = box[2]-box[0]
+ h = box[3]-box[1]
+
+ if method == AFFINE:
+ # change argument order to match implementation
+ data = (data[2], data[0], data[1],
+ data[5], data[3], data[4])
+ elif method == EXTENT:
+ # convert extent to an affine transform
+ x0, y0, x1, y1 = data
+ xs = float(x1 - x0) / w
+ ys = float(y1 - y0) / h
+ method = AFFINE
+ data = (x0 + xs/2, xs, 0, y0 + ys/2, 0, ys)
+ elif method == PERSPECTIVE:
+ # change argument order to match implementation
+ data = (data[2], data[0], data[1],
+ data[5], data[3], data[4],
+ data[6], data[7])
+ elif method == QUAD:
+ # quadrilateral warp. data specifies the four corners
+ # given as NW, SW, SE, and NE.
+ nw = data[0:2]
+ sw = data[2:4]
+ se = data[4:6]
+ ne = data[6:8]
+ x0, y0 = nw
+ As = 1.0 / w
+ At = 1.0 / h
+ data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At,
+ (se[0]-sw[0]-ne[0]+x0)*As*At,
+ y0, (ne[1]-y0)*As, (sw[1]-y0)*At,
+ (se[1]-sw[1]-ne[1]+y0)*As*At)
+ else:
+ raise ValueError("unknown transformation method")
+
+ if resample not in (NEAREST, BILINEAR, BICUBIC):
+ raise ValueError("unknown resampling filter")
+
+ image.load()
+
+ self.load()
+
+ if image.mode in ("1", "P"):
+ resample = NEAREST
+
+ self.im.transform2(box, image.im, method, data, resample, fill)
+
+ def transpose(self, method):
+ """
+ Transpose image (flip or rotate in 90 degree steps)
+
+ :param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`,
+ :py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`,
+ :py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270` or
+ :py:attr:`PIL.Image.TRANSPOSE`.
+ :returns: Returns a flipped or rotated copy of this image.
+ """
+
+ self.load()
+ return self._new(self.im.transpose(method))
+
+ def effect_spread(self, distance):
+ """
+ Randomly spread pixels in an image.
+
+ :param distance: Distance to spread pixels.
+ """
+ self.load()
+ im = self.im.effect_spread(distance)
+ return self._new(im)
+
+
+# --------------------------------------------------------------------
+# Lazy operations
+
+class _ImageCrop(Image):
+
+ def __init__(self, im, box):
+
+ Image.__init__(self)
+
+ x0, y0, x1, y1 = box
+ if x1 < x0:
+ x1 = x0
+ if y1 < y0:
+ y1 = y0
+
+ self.mode = im.mode
+ self.size = x1-x0, y1-y0
+
+ self.__crop = x0, y0, x1, y1
+
+ self.im = im.im
+
+ def load(self):
+
+ # lazy evaluation!
+ if self.__crop:
+ self.im = self.im.crop(self.__crop)
+ self.__crop = None
+
+ if self.im:
+ return self.im.pixel_access(self.readonly)
+
+ # FIXME: future versions should optimize crop/paste
+ # sequences!
+
+
+# --------------------------------------------------------------------
+# Abstract handlers.
+
+class ImagePointHandler:
+ # used as a mixin by point transforms (for use with im.point)
+ pass
+
+
+class ImageTransformHandler:
+ # used as a mixin by geometry transforms (for use with im.transform)
+ pass
+
+
+# --------------------------------------------------------------------
+# Factories
+
+#
+# Debugging
+
+def _wedge():
+ "Create greyscale wedge (for debugging only)"
+
+ return Image()._new(core.wedge("L"))
+
+
+def new(mode, size, color=0):
+ """
+ Creates a new image with the given mode and size.
+
+ :param mode: The mode to use for the new image. See:
+ :ref:`concept-modes`.
+ :param size: A 2-tuple, containing (width, height) in pixels.
+ :param color: What color to use for the image. Default is black.
+ If given, this should be a single integer or floating point value
+ for single-band modes, and a tuple for multi-band modes (one value
+ per band). When creating RGB images, you can also use color
+ strings as supported by the ImageColor module. If the color is
+ None, the image is not initialised.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ if color is None:
+ # don't initialize
+ return Image()._new(core.new(mode, size))
+
+ if isStringType(color):
+ # css3-style specifier
+
+ from PIL import ImageColor
+ color = ImageColor.getcolor(color, mode)
+
+ return Image()._new(core.fill(mode, size, color))
+
+
+def frombytes(mode, size, data, decoder_name="raw", *args):
+ """
+ Creates a copy of an image memory from pixel data in a buffer.
+
+ In its simplest form, this function takes three arguments
+ (mode, size, and unpacked pixel data).
+
+ You can also use any pixel decoder supported by PIL. For more
+ information on available decoders, see the section
+ :ref:`Writing Your Own File Decoder <file-decoders>`.
+
+ Note that this function decodes pixel data only, not entire images.
+ If you have an entire image in a string, wrap it in a
+ :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load
+ it.
+
+ :param mode: The image mode. See: :ref:`concept-modes`.
+ :param size: The image size.
+ :param data: A byte buffer containing raw data for the given mode.
+ :param decoder_name: What decoder to use.
+ :param args: Additional parameters for the given decoder.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ if decoder_name == "raw" and args == ():
+ args = mode
+
+ im = new(mode, size)
+ im.frombytes(data, decoder_name, args)
+ return im
+
+
+def fromstring(*args, **kw):
+ """Deprecated alias to frombytes.
+
+ .. deprecated:: 2.0
+ """
+ warnings.warn(
+ 'fromstring() is deprecated. Please call frombytes() instead.',
+ DeprecationWarning,
+ stacklevel=2
+ )
+ return frombytes(*args, **kw)
+
+
+def frombuffer(mode, size, data, decoder_name="raw", *args):
+ """
+ Creates an image memory referencing pixel data in a byte buffer.
+
+ This function is similar to :py:func:`~PIL.Image.frombytes`, but uses data
+ in the byte buffer, where possible. This means that changes to the
+ original buffer object are reflected in this image). Not all modes can
+ share memory; supported modes include "L", "RGBX", "RGBA", and "CMYK".
+
+ Note that this function decodes pixel data only, not entire images.
+ If you have an entire image file in a string, wrap it in a
+ **BytesIO** object, and use :py:func:`~PIL.Image.open` to load it.
+
+ In the current version, the default parameters used for the "raw" decoder
+ differs from that used for :py:func:`~PIL.Image.fromstring`. This is a
+ bug, and will probably be fixed in a future release. The current release
+ issues a warning if you do this; to disable the warning, you should provide
+ the full set of parameters. See below for details.
+
+ :param mode: The image mode. See: :ref:`concept-modes`.
+ :param size: The image size.
+ :param data: A bytes or other buffer object containing raw
+ data for the given mode.
+ :param decoder_name: What decoder to use.
+ :param args: Additional parameters for the given decoder. For the
+ default encoder ("raw"), it's recommended that you provide the
+ full set of parameters::
+
+ frombuffer(mode, size, data, "raw", mode, 0, 1)
+
+ :returns: An :py:class:`~PIL.Image.Image` object.
+
+ .. versionadded:: 1.1.4
+ """
+
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ if decoder_name == "raw":
+ if args == ():
+ if warnings:
+ warnings.warn(
+ "the frombuffer defaults may change in a future release; "
+ "for portability, change the call to read:\n"
+ " frombuffer(mode, size, data, 'raw', mode, 0, 1)",
+ RuntimeWarning, stacklevel=2
+ )
+ args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6
+ if args[0] in _MAPMODES:
+ im = new(mode, (1, 1))
+ im = im._new(
+ core.map_buffer(data, size, decoder_name, None, 0, args)
+ )
+ im.readonly = 1
+ return im
+
+ return frombytes(mode, size, data, decoder_name, args)
+
+
+def fromarray(obj, mode=None):
+ """
+ Creates an image memory from an object exporting the array interface
+ (using the buffer protocol).
+
+ If obj is not contiguous, then the tobytes method is called
+ and :py:func:`~PIL.Image.frombuffer` is used.
+
+ :param obj: Object with array interface
+ :param mode: Mode to use (will be determined from type if None)
+ See: :ref:`concept-modes`.
+ :returns: An image object.
+
+ .. versionadded:: 1.1.6
+ """
+ arr = obj.__array_interface__
+ shape = arr['shape']
+ ndim = len(shape)
+ try:
+ strides = arr['strides']
+ except KeyError:
+ strides = None
+ if mode is None:
+ try:
+ typekey = (1, 1) + shape[2:], arr['typestr']
+ mode, rawmode = _fromarray_typemap[typekey]
+ except KeyError:
+ # print typekey
+ raise TypeError("Cannot handle this data type")
+ else:
+ rawmode = mode
+ if mode in ["1", "L", "I", "P", "F"]:
+ ndmax = 2
+ elif mode == "RGB":
+ ndmax = 3
+ else:
+ ndmax = 4
+ if ndim > ndmax:
+ raise ValueError("Too many dimensions: %d > %d." % (ndim, ndmax))
+
+ size = shape[1], shape[0]
+ if strides is not None:
+ if hasattr(obj, 'tobytes'):
+ obj = obj.tobytes()
+ else:
+ obj = obj.tostring()
+
+ return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
+
+_fromarray_typemap = {
+ # (shape, typestr) => mode, rawmode
+ # first two members of shape are set to one
+ # ((1, 1), "|b1"): ("1", "1"), # broken
+ ((1, 1), "|u1"): ("L", "L"),
+ ((1, 1), "|i1"): ("I", "I;8"),
+ ((1, 1), "<i2"): ("I", "I;16"),
+ ((1, 1), ">i2"): ("I", "I;16B"),
+ ((1, 1), "<i4"): ("I", "I;32"),
+ ((1, 1), ">i4"): ("I", "I;32B"),
+ ((1, 1), "<f4"): ("F", "F;32F"),
+ ((1, 1), ">f4"): ("F", "F;32BF"),
+ ((1, 1), "<f8"): ("F", "F;64F"),
+ ((1, 1), ">f8"): ("F", "F;64BF"),
+ ((1, 1, 3), "|u1"): ("RGB", "RGB"),
+ ((1, 1, 4), "|u1"): ("RGBA", "RGBA"),
+ }
+
+# shortcuts
+_fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I")
+_fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
+
+
+def _decompression_bomb_check(size):
+ if MAX_IMAGE_PIXELS is None:
+ return
+
+ pixels = size[0] * size[1]
+
+ if pixels > MAX_IMAGE_PIXELS:
+ warnings.warn(
+ "Image size (%d pixels) exceeds limit of %d pixels, "
+ "could be decompression bomb DOS attack." %
+ (pixels, MAX_IMAGE_PIXELS),
+ DecompressionBombWarning)
+
+
+def open(fp, mode="r"):
+ """
+ Opens and identifies the given image file.
+
+ This is a lazy operation; this function identifies the file, but
+ the file remains open and the actual image data is not read from
+ the file until you try to process the data (or call the
+ :py:meth:`~PIL.Image.Image.load` method). See
+ :py:func:`~PIL.Image.new`.
+
+ :param file: A filename (string) or a file object. The file object
+ must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
+ :py:meth:`~file.tell` methods, and be opened in binary mode.
+ :param mode: The mode. If given, this argument must be "r".
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ :exception IOError: If the file cannot be found, or the image cannot be
+ opened and identified.
+ """
+
+ if mode != "r":
+ raise ValueError("bad mode %r" % mode)
+
+ if isPath(fp):
+ filename = fp
+ fp = builtins.open(fp, "rb")
+ else:
+ filename = ""
+
+ try:
+ fp.seek(0)
+ except (AttributeError, io.UnsupportedOperation):
+ fp = io.BytesIO(fp.read())
+
+ prefix = fp.read(16)
+
+ preinit()
+
+ for i in ID:
+ try:
+ factory, accept = OPEN[i]
+ if not accept or accept(prefix):
+ fp.seek(0)
+ im = factory(fp, filename)
+ _decompression_bomb_check(im.size)
+ return im
+ except (SyntaxError, IndexError, TypeError, struct.error):
+ # import traceback
+ # traceback.print_exc()
+ pass
+
+ if init():
+
+ for i in ID:
+ try:
+ factory, accept = OPEN[i]
+ if not accept or accept(prefix):
+ fp.seek(0)
+ im = factory(fp, filename)
+ _decompression_bomb_check(im.size)
+ return im
+ except (SyntaxError, IndexError, TypeError, struct.error):
+ # import traceback
+ # traceback.print_exc()
+ pass
+
+ raise IOError("cannot identify image file %r"
+ % (filename if filename else fp))
+
+
+#
+# Image processing.
+
+def alpha_composite(im1, im2):
+ """
+ Alpha composite im2 over im1.
+
+ :param im1: The first image.
+ :param im2: The second image. Must have the same mode and size as
+ the first image.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ im1.load()
+ im2.load()
+ return im1._new(core.alpha_composite(im1.im, im2.im))
+
+
+def blend(im1, im2, alpha):
+ """
+ Creates a new image by interpolating between two input images, using
+ a constant alpha.::
+
+ out = image1 * (1.0 - alpha) + image2 * alpha
+
+ :param im1: The first image.
+ :param im2: The second image. Must have the same mode and size as
+ the first image.
+ :param alpha: The interpolation alpha factor. If alpha is 0.0, a
+ copy of the first image is returned. If alpha is 1.0, a copy of
+ the second image is returned. There are no restrictions on the
+ alpha value. If necessary, the result is clipped to fit into
+ the allowed output range.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ im1.load()
+ im2.load()
+ return im1._new(core.blend(im1.im, im2.im, alpha))
+
+
+def composite(image1, image2, mask):
+ """
+ Create composite image by blending images using a transparency mask.
+
+ :param image1: The first image.
+ :param image2: The second image. Must have the same mode and
+ size as the first image.
+ :param mask: A mask image. This image can have mode
+ "1", "L", or "RGBA", and must have the same size as the
+ other two images.
+ """
+
+ image = image2.copy()
+ image.paste(image1, None, mask)
+ return image
+
+
+def eval(image, *args):
+ """
+ Applies the function (which should take one argument) to each pixel
+ in the given image. If the image has more than one band, the same
+ function is applied to each band. Note that the function is
+ evaluated once for each possible pixel value, so you cannot use
+ random components or other generators.
+
+ :param image: The input image.
+ :param function: A function object, taking one integer argument.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ return image.point(args[0])
+
+
+def merge(mode, bands):
+ """
+ Merge a set of single band images into a new multiband image.
+
+ :param mode: The mode to use for the output image. See:
+ :ref:`concept-modes`.
+ :param bands: A sequence containing one single-band image for
+ each band in the output image. All bands must have the
+ same size.
+ :returns: An :py:class:`~PIL.Image.Image` object.
+ """
+
+ if getmodebands(mode) != len(bands) or "*" in mode:
+ raise ValueError("wrong number of bands")
+ for im in bands[1:]:
+ if im.mode != getmodetype(mode):
+ raise ValueError("mode mismatch")
+ if im.size != bands[0].size:
+ raise ValueError("size mismatch")
+ im = core.new(mode, bands[0].size)
+ for i in range(getmodebands(mode)):
+ bands[i].load()
+ im.putband(bands[i].im, i)
+ return bands[0]._new(im)
+
+
+# --------------------------------------------------------------------
+# Plugin registry
+
+def register_open(id, factory, accept=None):
+ """
+ Register an image file plugin. This function should not be used
+ in application code.
+
+ :param id: An image format identifier.
+ :param factory: An image file factory method.
+ :param accept: An optional function that can be used to quickly
+ reject images having another format.
+ """
+ id = id.upper()
+ ID.append(id)
+ OPEN[id] = factory, accept
+
+
+def register_mime(id, mimetype):
+ """
+ Registers an image MIME type. This function should not be used
+ in application code.
+
+ :param id: An image format identifier.
+ :param mimetype: The image MIME type for this format.
+ """
+ MIME[id.upper()] = mimetype
+
+
+def register_save(id, driver):
+ """
+ Registers an image save function. This function should not be
+ used in application code.
+
+ :param id: An image format identifier.
+ :param driver: A function to save images in this format.
+ """
+ SAVE[id.upper()] = driver
+
+
+def register_extension(id, extension):
+ """
+ Registers an image extension. This function should not be
+ used in application code.
+
+ :param id: An image format identifier.
+ :param extension: An extension used for this format.
+ """
+ EXTENSION[extension.lower()] = id.upper()
+
+
+# --------------------------------------------------------------------
+# Simple display support. User code may override this.
+
+def _show(image, **options):
+ # override me, as necessary
+ _showxv(image, **options)
+
+
+def _showxv(image, title=None, **options):
+ from PIL import ImageShow
+ ImageShow.show(image, title, **options)
+
+
+# --------------------------------------------------------------------
+# Effects
+
+def effect_mandelbrot(size, extent, quality):
+ """
+ Generate a Mandelbrot set covering the given extent.
+
+ :param size: The requested size in pixels, as a 2-tuple:
+ (width, height).
+ :param extent: The extent to cover, as a 4-tuple:
+ (x0, y0, x1, y2).
+ :param quality: Quality.
+ """
+ return Image()._new(core.effect_mandelbrot(size, extent, quality))
+
+
+def effect_noise(size, sigma):
+ """
+ Generate Gaussian noise centered around 128.
+
+ :param size: The requested size in pixels, as a 2-tuple:
+ (width, height).
+ :param sigma: Standard deviation of noise.
+ """
+ return Image()._new(core.effect_noise(size, sigma))
+
+# End of file
diff --git a/lib/Python/Lib/PIL/ImageChops.py b/lib/Python/Lib/PIL/ImageChops.py
new file mode 100644
index 000000000..ba5350e02
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageChops.py
@@ -0,0 +1,283 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# standard channel operations
+#
+# History:
+# 1996-03-24 fl Created
+# 1996-08-13 fl Added logical operations (for "1" images)
+# 2000-10-12 fl Added offset method (from Image.py)
+#
+# Copyright (c) 1997-2000 by Secret Labs AB
+# Copyright (c) 1996-2000 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+
+
+def constant(image, value):
+ """Fill a channel with a given grey level.
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ return Image.new("L", image.size, value)
+
+
+def duplicate(image):
+ """Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`.
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ return image.copy()
+
+
+def invert(image):
+ """
+ Invert an image (channel).
+
+ .. code-block:: python
+
+ out = MAX - image
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image.load()
+ return image._new(image.im.chop_invert())
+
+
+def lighter(image1, image2):
+ """
+ Compares the two images, pixel by pixel, and returns a new image containing
+ the lighter values.
+
+ .. code-block:: python
+
+ out = max(image1, image2)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_lighter(image2.im))
+
+
+def darker(image1, image2):
+ """
+ Compares the two images, pixel by pixel, and returns a new image
+ containing the darker values.
+
+ .. code-block:: python
+
+ out = min(image1, image2)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_darker(image2.im))
+
+
+def difference(image1, image2):
+ """
+ Returns the absolute value of the pixel-by-pixel difference between the two
+ images.
+
+ .. code-block:: python
+
+ out = abs(image1 - image2)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_difference(image2.im))
+
+
+def multiply(image1, image2):
+ """
+ Superimposes two images on top of each other.
+
+ If you multiply an image with a solid black image, the result is black. If
+ you multiply with a solid white image, the image is unaffected.
+
+ .. code-block:: python
+
+ out = image1 * image2 / MAX
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_multiply(image2.im))
+
+
+def screen(image1, image2):
+ """
+ Superimposes two inverted images on top of each other.
+
+ .. code-block:: python
+
+ out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_screen(image2.im))
+
+
+def add(image1, image2, scale=1.0, offset=0):
+ """
+ Adds two images, dividing the result by scale and adding the
+ offset. If omitted, scale defaults to 1.0, and offset to 0.0.
+
+ .. code-block:: python
+
+ out = ((image1 + image2) / scale + offset)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_add(image2.im, scale, offset))
+
+
+def subtract(image1, image2, scale=1.0, offset=0):
+ """
+ Subtracts two images, dividing the result by scale and adding the
+ offset. If omitted, scale defaults to 1.0, and offset to 0.0.
+
+ .. code-block:: python
+
+ out = ((image1 - image2) / scale + offset)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_subtract(image2.im, scale, offset))
+
+
+def add_modulo(image1, image2):
+ """Add two images, without clipping the result.
+
+ .. code-block:: python
+
+ out = ((image1 + image2) % MAX)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_add_modulo(image2.im))
+
+
+def subtract_modulo(image1, image2):
+ """Subtract two images, without clipping the result.
+
+ .. code-block:: python
+
+ out = ((image1 - image2) % MAX)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_subtract_modulo(image2.im))
+
+
+def logical_and(image1, image2):
+ """Logical AND between two images.
+
+ .. code-block:: python
+
+ out = ((image1 and image2) % MAX)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_and(image2.im))
+
+
+def logical_or(image1, image2):
+ """Logical OR between two images.
+
+ .. code-block:: python
+
+ out = ((image1 or image2) % MAX)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_or(image2.im))
+
+
+def logical_xor(image1, image2):
+ """Logical XOR between two images.
+
+ .. code-block:: python
+
+ out = ((bool(image1) != bool(image2)) % MAX)
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ image1.load()
+ image2.load()
+ return image1._new(image1.im.chop_xor(image2.im))
+
+
+def blend(image1, image2, alpha):
+ """Blend images using constant transparency weight. Alias for
+ :py:meth:`PIL.Image.Image.blend`.
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ return Image.blend(image1, image2, alpha)
+
+
+def composite(image1, image2, mask):
+ """Create composite using transparency mask. Alias for
+ :py:meth:`PIL.Image.Image.composite`.
+
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ return Image.composite(image1, image2, mask)
+
+
+def offset(image, xoffset, yoffset=None):
+ """Returns a copy of the image where data has been offset by the given
+ distances. Data wraps around the edges. If **yoffset** is omitted, it
+ is assumed to be equal to **xoffset**.
+
+ :param xoffset: The horizontal distance.
+ :param yoffset: The vertical distance. If omitted, both
+ distances are set to the same value.
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+
+ if yoffset is None:
+ yoffset = xoffset
+ image.load()
+ return image._new(image.im.offset(xoffset, yoffset))
diff --git a/lib/Python/Lib/PIL/ImageCms.py b/lib/Python/Lib/PIL/ImageCms.py
new file mode 100644
index 000000000..ed219f7ba
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageCms.py
@@ -0,0 +1,972 @@
+# The Python Imaging Library.
+# $Id$
+
+# Optional color managment support, based on Kevin Cazabon's PyCMS
+# library.
+
+# History:
+
+# 2009-03-08 fl Added to PIL.
+
+# Copyright (C) 2002-2003 Kevin Cazabon
+# Copyright (c) 2009 by Fredrik Lundh
+# Copyright (c) 2013 by Eric Soroos
+
+# See the README file for information on usage and redistribution. See
+# below for the original description.
+
+from __future__ import print_function
+
+DESCRIPTION = """
+pyCMS
+
+ a Python / PIL interface to the littleCMS ICC Color Management System
+ Copyright (C) 2002-2003 Kevin Cazabon
+ kevin@cazabon.com
+ http://www.cazabon.com
+
+ pyCMS home page: http://www.cazabon.com/pyCMS
+ littleCMS home page: http://www.littlecms.com
+ (littleCMS is Copyright (C) 1998-2001 Marti Maria)
+
+ Originally released under LGPL. Graciously donated to PIL in
+ March 2009, for distribution under the standard PIL license
+
+ The pyCMS.py module provides a "clean" interface between Python/PIL and
+ pyCMSdll, taking care of some of the more complex handling of the direct
+ pyCMSdll functions, as well as error-checking and making sure that all
+ relevant data is kept together.
+
+ While it is possible to call pyCMSdll functions directly, it's not highly
+ recommended.
+
+ Version History:
+
+ 1.0.0 pil Oct 2013 Port to LCMS 2.
+
+ 0.1.0 pil mod March 10, 2009
+
+ Renamed display profile to proof profile. The proof
+ profile is the profile of the device that is being
+ simulated, not the profile of the device which is
+ actually used to display/print the final simulation
+ (that'd be the output profile) - also see LCMSAPI.txt
+ input colorspace -> using 'renderingIntent' -> proof
+ colorspace -> using 'proofRenderingIntent' -> output
+ colorspace
+
+ Added LCMS FLAGS support.
+ Added FLAGS["SOFTPROOFING"] as default flag for
+ buildProofTransform (otherwise the proof profile/intent
+ would be ignored).
+
+ 0.1.0 pil March 2009 - added to PIL, as PIL.ImageCms
+
+ 0.0.2 alpha Jan 6, 2002
+
+ Added try/except statements arount type() checks of
+ potential CObjects... Python won't let you use type()
+ on them, and raises a TypeError (stupid, if you ask
+ me!)
+
+ Added buildProofTransformFromOpenProfiles() function.
+ Additional fixes in DLL, see DLL code for details.
+
+ 0.0.1 alpha first public release, Dec. 26, 2002
+
+ Known to-do list with current version (of Python interface, not pyCMSdll):
+
+ none
+
+"""
+
+VERSION = "1.0.0 pil"
+
+# --------------------------------------------------------------------.
+
+from PIL import Image
+try:
+ from PIL import _imagingcms
+except ImportError as ex:
+ # Allow error import for doc purposes, but error out when accessing
+ # anything in core.
+ from _util import deferred_error
+ _imagingcms = deferred_error(ex)
+from PIL._util import isStringType
+
+core = _imagingcms
+
+#
+# intent/direction values
+
+INTENT_PERCEPTUAL = 0
+INTENT_RELATIVE_COLORIMETRIC = 1
+INTENT_SATURATION = 2
+INTENT_ABSOLUTE_COLORIMETRIC = 3
+
+DIRECTION_INPUT = 0
+DIRECTION_OUTPUT = 1
+DIRECTION_PROOF = 2
+
+#
+# flags
+
+FLAGS = {
+ "MATRIXINPUT": 1,
+ "MATRIXOUTPUT": 2,
+ "MATRIXONLY": (1 | 2),
+ "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot
+ # Don't create prelinearization tables on precalculated transforms
+ # (internal use):
+ "NOPRELINEARIZATION": 16,
+ "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink)
+ "NOTCACHE": 64, # Inhibit 1-pixel cache
+ "NOTPRECALC": 256,
+ "NULLTRANSFORM": 512, # Don't transform anyway
+ "HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
+ "LOWRESPRECALC": 2048, # Use less memory to minimize resouces
+ "WHITEBLACKCOMPENSATION": 8192,
+ "BLACKPOINTCOMPENSATION": 8192,
+ "GAMUTCHECK": 4096, # Out of Gamut alarm
+ "SOFTPROOFING": 16384, # Do softproofing
+ "PRESERVEBLACK": 32768, # Black preservation
+ "NODEFAULTRESOURCEDEF": 16777216, # CRD special
+ "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints
+}
+
+_MAX_FLAG = 0
+for flag in FLAGS.values():
+ if isinstance(flag, int):
+ _MAX_FLAG = _MAX_FLAG | flag
+
+
+# --------------------------------------------------------------------.
+# Experimental PIL-level API
+# --------------------------------------------------------------------.
+
+##
+# Profile.
+
+class ImageCmsProfile:
+
+ def __init__(self, profile):
+ """
+ :param profile: Either a string representing a filename,
+ a file like object containing a profile or a
+ low-level profile object
+
+ """
+
+ if isStringType(profile):
+ self._set(core.profile_open(profile), profile)
+ elif hasattr(profile, "read"):
+ self._set(core.profile_frombytes(profile.read()))
+ else:
+ self._set(profile) # assume it's already a profile
+
+ def _set(self, profile, filename=None):
+ self.profile = profile
+ self.filename = filename
+ if profile:
+ self.product_name = None # profile.product_name
+ self.product_info = None # profile.product_info
+ else:
+ self.product_name = None
+ self.product_info = None
+
+ def tobytes(self):
+ """
+ Returns the profile in a format suitable for embedding in
+ saved images.
+
+ :returns: a bytes object containing the ICC profile.
+ """
+
+ return core.profile_tobytes(self.profile)
+
+
+class ImageCmsTransform(Image.ImagePointHandler):
+
+ # Transform. This can be used with the procedural API, or with the
+ # standard Image.point() method.
+ #
+ # Will return the output profile in the output.info['icc_profile'].
+
+ def __init__(self, input, output, input_mode, output_mode,
+ intent=INTENT_PERCEPTUAL, proof=None,
+ proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0):
+ if proof is None:
+ self.transform = core.buildTransform(
+ input.profile, output.profile,
+ input_mode, output_mode,
+ intent,
+ flags
+ )
+ else:
+ self.transform = core.buildProofTransform(
+ input.profile, output.profile, proof.profile,
+ input_mode, output_mode,
+ intent, proof_intent,
+ flags
+ )
+ # Note: inputMode and outputMode are for pyCMS compatibility only
+ self.input_mode = self.inputMode = input_mode
+ self.output_mode = self.outputMode = output_mode
+
+ self.output_profile = output
+
+ def point(self, im):
+ return self.apply(im)
+
+ def apply(self, im, imOut=None):
+ im.load()
+ if imOut is None:
+ imOut = Image.new(self.output_mode, im.size, None)
+ self.transform.apply(im.im.id, imOut.im.id)
+ imOut.info['icc_profile'] = self.output_profile.tobytes()
+ return imOut
+
+ def apply_in_place(self, im):
+ im.load()
+ if im.mode != self.output_mode:
+ raise ValueError("mode mismatch") # wrong output mode
+ self.transform.apply(im.im.id, im.im.id)
+ im.info['icc_profile'] = self.output_profile.tobytes()
+ return im
+
+
+def get_display_profile(handle=None):
+ """ (experimental) Fetches the profile for the current display device.
+ :returns: None if the profile is not known.
+ """
+
+ import sys
+ if sys.platform == "win32":
+ from PIL import ImageWin
+ if isinstance(handle, ImageWin.HDC):
+ profile = core.get_display_profile_win32(handle, 1)
+ else:
+ profile = core.get_display_profile_win32(handle or 0)
+ else:
+ try:
+ get = _imagingcms.get_display_profile
+ except AttributeError:
+ return None
+ else:
+ profile = get()
+ return ImageCmsProfile(profile)
+
+
+# --------------------------------------------------------------------.
+# pyCMS compatible layer
+# --------------------------------------------------------------------.
+
+class PyCMSError(Exception):
+
+ """ (pyCMS) Exception class.
+ This is used for all errors in the pyCMS API. """
+ pass
+
+
+def profileToProfile(
+ im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL,
+ outputMode=None, inPlace=0, flags=0):
+ """
+ (pyCMS) Applies an ICC transformation to a given image, mapping from
+ inputProfile to outputProfile.
+
+ If the input or output profiles specified are not valid filenames, a
+ PyCMSError will be raised. If inPlace == TRUE and outputMode != im.mode,
+ a PyCMSError will be raised. If an error occurs during application of
+ the profiles, a PyCMSError will be raised. If outputMode is not a mode
+ supported by the outputProfile (or by pyCMS), a PyCMSError will be
+ raised.
+
+ This function applies an ICC transformation to im from inputProfile's
+ color space to outputProfile's color space using the specified rendering
+ intent to decide how to handle out-of-gamut colors.
+
+ OutputMode can be used to specify that a color mode conversion is to
+ be done using these profiles, but the specified profiles must be able
+ to handle that mode. I.e., if converting im from RGB to CMYK using
+ profiles, the input profile must handle RGB data, and the output
+ profile must handle CMYK data.
+
+ :param im: An open PIL image object (i.e. Image.new(...) or
+ Image.open(...), etc.)
+ :param inputProfile: String, as a valid filename path to the ICC input
+ profile you wish to use for this image, or a profile object
+ :param outputProfile: String, as a valid filename path to the ICC output
+ profile you wish to use for this image, or a profile object
+ :param renderingIntent: Integer (0-3) specifying the rendering intent you
+ wish to use for the transform
+
+ INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
+ INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
+ INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
+ INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
+
+ see the pyCMS documentation for details on rendering intents and what
+ they do.
+ :param outputMode: A valid PIL mode for the output image (i.e. "RGB",
+ "CMYK", etc.). Note: if rendering the image "inPlace", outputMode
+ MUST be the same mode as the input, or omitted completely. If
+ omitted, the outputMode will be the same as the mode of the input
+ image (im.mode)
+ :param inPlace: Boolean (1 = True, None or 0 = False). If True, the
+ original image is modified in-place, and None is returned. If False
+ (default), a new Image object is returned with the transform applied.
+ :param flags: Integer (0-...) specifying additional flags
+ :returns: Either None or a new PIL image object, depending on value of
+ inPlace
+ :exception PyCMSError:
+ """
+
+ if outputMode is None:
+ outputMode = im.mode
+
+ if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
+ raise PyCMSError("renderingIntent must be an integer between 0 and 3")
+
+ if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
+ raise PyCMSError(
+ "flags must be an integer between 0 and %s" + _MAX_FLAG)
+
+ try:
+ if not isinstance(inputProfile, ImageCmsProfile):
+ inputProfile = ImageCmsProfile(inputProfile)
+ if not isinstance(outputProfile, ImageCmsProfile):
+ outputProfile = ImageCmsProfile(outputProfile)
+ transform = ImageCmsTransform(
+ inputProfile, outputProfile, im.mode, outputMode,
+ renderingIntent, flags=flags
+ )
+ if inPlace:
+ transform.apply_in_place(im)
+ imOut = None
+ else:
+ imOut = transform.apply(im)
+ except (IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+ return imOut
+
+
+def getOpenProfile(profileFilename):
+ """
+ (pyCMS) Opens an ICC profile file.
+
+ The PyCMSProfile object can be passed back into pyCMS for use in creating
+ transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
+
+ If profileFilename is not a vaild filename for an ICC profile, a PyCMSError
+ will be raised.
+
+ :param profileFilename: String, as a valid filename path to the ICC profile
+ you wish to open, or a file-like object.
+ :returns: A CmsProfile class object.
+ :exception PyCMSError:
+ """
+
+ try:
+ return ImageCmsProfile(profileFilename)
+ except (IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def buildTransform(
+ inputProfile, outputProfile, inMode, outMode,
+ renderingIntent=INTENT_PERCEPTUAL, flags=0):
+ """
+ (pyCMS) Builds an ICC transform mapping from the inputProfile to the
+ outputProfile. Use applyTransform to apply the transform to a given
+ image.
+
+ If the input or output profiles specified are not valid filenames, a
+ PyCMSError will be raised. If an error occurs during creation of the
+ transform, a PyCMSError will be raised.
+
+ If inMode or outMode are not a mode supported by the outputProfile (or
+ by pyCMS), a PyCMSError will be raised.
+
+ This function builds and returns an ICC transform from the inputProfile
+ to the outputProfile using the renderingIntent to determine what to do
+ with out-of-gamut colors. It will ONLY work for converting images that
+ are in inMode to images that are in outMode color format (PIL mode,
+ i.e. "RGB", "RGBA", "CMYK", etc.).
+
+ Building the transform is a fair part of the overhead in
+ ImageCms.profileToProfile(), so if you're planning on converting multiple
+ images using the same input/output settings, this can save you time.
+ Once you have a transform object, it can be used with
+ ImageCms.applyProfile() to convert images without the need to re-compute
+ the lookup table for the transform.
+
+ The reason pyCMS returns a class object rather than a handle directly
+ to the transform is that it needs to keep track of the PIL input/output
+ modes that the transform is meant for. These attributes are stored in
+ the "inMode" and "outMode" attributes of the object (which can be
+ manually overridden if you really want to, but I don't know of any
+ time that would be of use, or would even work).
+
+ :param inputProfile: String, as a valid filename path to the ICC input
+ profile you wish to use for this transform, or a profile object
+ :param outputProfile: String, as a valid filename path to the ICC output
+ profile you wish to use for this transform, or a profile object
+ :param inMode: String, as a valid PIL mode that the appropriate profile
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+ :param outMode: String, as a valid PIL mode that the appropriate profile
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+ :param renderingIntent: Integer (0-3) specifying the rendering intent you
+ wish to use for the transform
+
+ INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
+ INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
+ INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
+ INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
+
+ see the pyCMS documentation for details on rendering intents and what
+ they do.
+ :param flags: Integer (0-...) specifying additional flags
+ :returns: A CmsTransform class object.
+ :exception PyCMSError:
+ """
+
+ if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
+ raise PyCMSError("renderingIntent must be an integer between 0 and 3")
+
+ if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
+ raise PyCMSError(
+ "flags must be an integer between 0 and %s" + _MAX_FLAG)
+
+ try:
+ if not isinstance(inputProfile, ImageCmsProfile):
+ inputProfile = ImageCmsProfile(inputProfile)
+ if not isinstance(outputProfile, ImageCmsProfile):
+ outputProfile = ImageCmsProfile(outputProfile)
+ return ImageCmsTransform(
+ inputProfile, outputProfile, inMode, outMode,
+ renderingIntent, flags=flags)
+ except (IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def buildProofTransform(
+ inputProfile, outputProfile, proofProfile, inMode, outMode,
+ renderingIntent=INTENT_PERCEPTUAL,
+ proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC,
+ flags=FLAGS["SOFTPROOFING"]):
+ """
+ (pyCMS) Builds an ICC transform mapping from the inputProfile to the
+ outputProfile, but tries to simulate the result that would be
+ obtained on the proofProfile device.
+
+ If the input, output, or proof profiles specified are not valid
+ filenames, a PyCMSError will be raised.
+
+ If an error occurs during creation of the transform, a PyCMSError will
+ be raised.
+
+ If inMode or outMode are not a mode supported by the outputProfile
+ (or by pyCMS), a PyCMSError will be raised.
+
+ This function builds and returns an ICC transform from the inputProfile
+ to the outputProfile, but tries to simulate the result that would be
+ obtained on the proofProfile device using renderingIntent and
+ proofRenderingIntent to determine what to do with out-of-gamut
+ colors. This is known as "soft-proofing". It will ONLY work for
+ converting images that are in inMode to images that are in outMode
+ color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.).
+
+ Usage of the resulting transform object is exactly the same as with
+ ImageCms.buildTransform().
+
+ Proof profiling is generally used when using an output device to get a
+ good idea of what the final printed/displayed image would look like on
+ the proofProfile device when it's quicker and easier to use the
+ output device for judging color. Generally, this means that the
+ output device is a monitor, or a dye-sub printer (etc.), and the simulated
+ device is something more expensive, complicated, or time consuming
+ (making it difficult to make a real print for color judgement purposes).
+
+ Soft-proofing basically functions by adjusting the colors on the
+ output device to match the colors of the device being simulated. However,
+ when the simulated device has a much wider gamut than the output
+ device, you may obtain marginal results.
+
+ :param inputProfile: String, as a valid filename path to the ICC input
+ profile you wish to use for this transform, or a profile object
+ :param outputProfile: String, as a valid filename path to the ICC output
+ (monitor, usually) profile you wish to use for this transform, or a
+ profile object
+ :param proofProfile: String, as a valid filename path to the ICC proof
+ profile you wish to use for this transform, or a profile object
+ :param inMode: String, as a valid PIL mode that the appropriate profile
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+ :param outMode: String, as a valid PIL mode that the appropriate profile
+ also supports (i.e. "RGB", "RGBA", "CMYK", etc.)
+ :param renderingIntent: Integer (0-3) specifying the rendering intent you
+ wish to use for the input->proof (simulated) transform
+
+ INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
+ INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
+ INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
+ INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
+
+ see the pyCMS documentation for details on rendering intents and what
+ they do.
+ :param proofRenderingIntent: Integer (0-3) specifying the rendering intent you
+ wish to use for proof->output transform
+
+ INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
+ INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
+ INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
+ INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
+
+ see the pyCMS documentation for details on rendering intents and what
+ they do.
+ :param flags: Integer (0-...) specifying additional flags
+ :returns: A CmsTransform class object.
+ :exception PyCMSError:
+ """
+
+ if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3):
+ raise PyCMSError("renderingIntent must be an integer between 0 and 3")
+
+ if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG):
+ raise PyCMSError(
+ "flags must be an integer between 0 and %s" + _MAX_FLAG)
+
+ try:
+ if not isinstance(inputProfile, ImageCmsProfile):
+ inputProfile = ImageCmsProfile(inputProfile)
+ if not isinstance(outputProfile, ImageCmsProfile):
+ outputProfile = ImageCmsProfile(outputProfile)
+ if not isinstance(proofProfile, ImageCmsProfile):
+ proofProfile = ImageCmsProfile(proofProfile)
+ return ImageCmsTransform(
+ inputProfile, outputProfile, inMode, outMode, renderingIntent,
+ proofProfile, proofRenderingIntent, flags)
+ except (IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+buildTransformFromOpenProfiles = buildTransform
+buildProofTransformFromOpenProfiles = buildProofTransform
+
+
+def applyTransform(im, transform, inPlace=0):
+ """
+ (pyCMS) Applies a transform to a given image.
+
+ If im.mode != transform.inMode, a PyCMSError is raised.
+
+ If inPlace == TRUE and transform.inMode != transform.outMode, a
+ PyCMSError is raised.
+
+ If im.mode, transfer.inMode, or transfer.outMode is not supported by
+ pyCMSdll or the profiles you used for the transform, a PyCMSError is
+ raised.
+
+ If an error occurs while the transform is being applied, a PyCMSError
+ is raised.
+
+ This function applies a pre-calculated transform (from
+ ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
+ to an image. The transform can be used for multiple images, saving
+ considerable calcuation time if doing the same conversion multiple times.
+
+ If you want to modify im in-place instead of receiving a new image as
+ the return value, set inPlace to TRUE. This can only be done if
+ transform.inMode and transform.outMode are the same, because we can't
+ change the mode in-place (the buffer sizes for some modes are
+ different). The default behavior is to return a new Image object of
+ the same dimensions in mode transform.outMode.
+
+ :param im: A PIL Image object, and im.mode must be the same as the inMode
+ supported by the transform.
+ :param transform: A valid CmsTransform class object
+ :param inPlace: Bool (1 == True, 0 or None == False). If True, im is
+ modified in place and None is returned, if False, a new Image object
+ with the transform applied is returned (and im is not changed). The
+ default is False.
+ :returns: Either None, or a new PIL Image object, depending on the value of
+ inPlace. The profile will be returned in the image's info['icc_profile'].
+ :exception PyCMSError:
+ """
+
+ try:
+ if inPlace:
+ transform.apply_in_place(im)
+ imOut = None
+ else:
+ imOut = transform.apply(im)
+ except (TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+ return imOut
+
+
+def createProfile(colorSpace, colorTemp=-1):
+ """
+ (pyCMS) Creates a profile.
+
+ If colorSpace not in ["LAB", "XYZ", "sRGB"], a PyCMSError is raised
+
+ If using LAB and colorTemp != a positive integer, a PyCMSError is raised.
+
+ If an error occurs while creating the profile, a PyCMSError is raised.
+
+ Use this function to create common profiles on-the-fly instead of
+ having to supply a profile on disk and knowing the path to it. It
+ returns a normal CmsProfile object that can be passed to
+ ImageCms.buildTransformFromOpenProfiles() to create a transform to apply
+ to images.
+
+ :param colorSpace: String, the color space of the profile you wish to
+ create.
+ Currently only "LAB", "XYZ", and "sRGB" are supported.
+ :param colorTemp: Positive integer for the white point for the profile, in
+ degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50
+ illuminant if omitted (5000k). colorTemp is ONLY applied to LAB
+ profiles, and is ignored for XYZ and sRGB.
+ :returns: A CmsProfile class object
+ :exception PyCMSError:
+ """
+
+ if colorSpace not in ["LAB", "XYZ", "sRGB"]:
+ raise PyCMSError(
+ "Color space not supported for on-the-fly profile creation (%s)"
+ % colorSpace)
+
+ if colorSpace == "LAB":
+ try:
+ colorTemp = float(colorTemp)
+ except:
+ raise PyCMSError(
+ "Color temperature must be numeric, \"%s\" not valid"
+ % colorTemp)
+
+ try:
+ return core.createProfile(colorSpace, colorTemp)
+ except (TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def getProfileName(profile):
+ """
+
+ (pyCMS) Gets the internal product name for the given profile.
+
+ If profile isn't a valid CmsProfile object or filename to a profile,
+ a PyCMSError is raised If an error occurs while trying to obtain the
+ name tag, a PyCMSError is raised.
+
+ Use this function to obtain the INTERNAL name of the profile (stored
+ in an ICC tag in the profile itself), usually the one used when the
+ profile was originally created. Sometimes this tag also contains
+ additional information supplied by the creator.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :returns: A string containing the internal name of the profile as stored
+ in an ICC tag.
+ :exception PyCMSError:
+ """
+
+ try:
+ # add an extra newline to preserve pyCMS compatibility
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ # do it in python, not c.
+ # // name was "%s - %s" (model, manufacturer) || Description ,
+ # // but if the Model and Manufacturer were the same or the model
+ # // was long, Just the model, in 1.x
+ model = profile.profile.product_model
+ manufacturer = profile.profile.product_manufacturer
+
+ if not (model or manufacturer):
+ return profile.profile.product_description + "\n"
+ if not manufacturer or len(model) > 30:
+ return model + "\n"
+ return "%s - %s\n" % (model, manufacturer)
+
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def getProfileInfo(profile):
+ """
+ (pyCMS) Gets the internal product information for the given profile.
+
+ If profile isn't a valid CmsProfile object or filename to a profile,
+ a PyCMSError is raised.
+
+ If an error occurs while trying to obtain the info tag, a PyCMSError
+ is raised
+
+ Use this function to obtain the information stored in the profile's
+ info tag. This often contains details about the profile, and how it
+ was created, as supplied by the creator.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :returns: A string containing the internal profile information stored in
+ an ICC tag.
+ :exception PyCMSError:
+ """
+
+ try:
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ # add an extra newline to preserve pyCMS compatibility
+ # Python, not C. the white point bits weren't working well,
+ # so skipping.
+ # // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
+ description = profile.profile.product_description
+ cpright = profile.profile.product_copyright
+ arr = []
+ for elt in (description, cpright):
+ if elt:
+ arr.append(elt)
+ return "\r\n\r\n".join(arr) + "\r\n\r\n"
+
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def getProfileCopyright(profile):
+ """
+ (pyCMS) Gets the copyright for the given profile.
+
+ If profile isn't a valid CmsProfile object or filename to a profile,
+ a PyCMSError is raised.
+
+ If an error occurs while trying to obtain the copyright tag, a PyCMSError
+ is raised
+
+ Use this function to obtain the information stored in the profile's
+ copyright tag.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :returns: A string containing the internal profile information stored in
+ an ICC tag.
+ :exception PyCMSError:
+ """
+ try:
+ # add an extra newline to preserve pyCMS compatibility
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ return profile.profile.product_copyright + "\n"
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def getProfileManufacturer(profile):
+ """
+ (pyCMS) Gets the manufacturer for the given profile.
+
+ If profile isn't a valid CmsProfile object or filename to a profile,
+ a PyCMSError is raised.
+
+ If an error occurs while trying to obtain the manufacturer tag, a
+ PyCMSError is raised
+
+ Use this function to obtain the information stored in the profile's
+ manufacturer tag.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :returns: A string containing the internal profile information stored in
+ an ICC tag.
+ :exception PyCMSError:
+ """
+ try:
+ # add an extra newline to preserve pyCMS compatibility
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ return profile.profile.product_manufacturer + "\n"
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def getProfileModel(profile):
+ """
+ (pyCMS) Gets the model for the given profile.
+
+ If profile isn't a valid CmsProfile object or filename to a profile,
+ a PyCMSError is raised.
+
+ If an error occurs while trying to obtain the model tag, a PyCMSError
+ is raised
+
+ Use this function to obtain the information stored in the profile's
+ model tag.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :returns: A string containing the internal profile information stored in
+ an ICC tag.
+ :exception PyCMSError:
+ """
+
+ try:
+ # add an extra newline to preserve pyCMS compatibility
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ return profile.profile.product_model + "\n"
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def getProfileDescription(profile):
+ """
+ (pyCMS) Gets the description for the given profile.
+
+ If profile isn't a valid CmsProfile object or filename to a profile,
+ a PyCMSError is raised.
+
+ If an error occurs while trying to obtain the description tag, a PyCMSError
+ is raised
+
+ Use this function to obtain the information stored in the profile's
+ description tag.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :returns: A string containing the internal profile information stored in an
+ ICC tag.
+ :exception PyCMSError:
+ """
+
+ try:
+ # add an extra newline to preserve pyCMS compatibility
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ return profile.profile.product_description + "\n"
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def getDefaultIntent(profile):
+ """
+ (pyCMS) Gets the default intent name for the given profile.
+
+ If profile isn't a valid CmsProfile object or filename to a profile,
+ a PyCMSError is raised.
+
+ If an error occurs while trying to obtain the default intent, a
+ PyCMSError is raised.
+
+ Use this function to determine the default (and usually best optomized)
+ rendering intent for this profile. Most profiles support multiple
+ rendering intents, but are intended mostly for one type of conversion.
+ If you wish to use a different intent than returned, use
+ ImageCms.isIntentSupported() to verify it will work first.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :returns: Integer 0-3 specifying the default rendering intent for this
+ profile.
+
+ INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
+ INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
+ INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
+ INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
+
+ see the pyCMS documentation for details on rendering intents and what
+ they do.
+ :exception PyCMSError:
+ """
+
+ try:
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ return profile.profile.rendering_intent
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def isIntentSupported(profile, intent, direction):
+ """
+ (pyCMS) Checks if a given intent is supported.
+
+ Use this function to verify that you can use your desired
+ renderingIntent with profile, and that profile can be used for the
+ input/output/proof profile as you desire.
+
+ Some profiles are created specifically for one "direction", can cannot
+ be used for others. Some profiles can only be used for certain
+ rendering intents... so it's best to either verify this before trying
+ to create a transform with them (using this function), or catch the
+ potential PyCMSError that will occur if they don't support the modes
+ you select.
+
+ :param profile: EITHER a valid CmsProfile object, OR a string of the
+ filename of an ICC profile.
+ :param intent: Integer (0-3) specifying the rendering intent you wish to
+ use with this profile
+
+ INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL)
+ INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC)
+ INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION)
+ INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC)
+
+ see the pyCMS documentation for details on rendering intents and what
+ they do.
+ :param direction: Integer specifing if the profile is to be used for input,
+ output, or proof
+
+ INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
+ OUTPUT = 1 (or use ImageCms.DIRECTION_OUTPUT)
+ PROOF = 2 (or use ImageCms.DIRECTION_PROOF)
+
+ :returns: 1 if the intent/direction are supported, -1 if they are not.
+ :exception PyCMSError:
+ """
+
+ try:
+ if not isinstance(profile, ImageCmsProfile):
+ profile = ImageCmsProfile(profile)
+ # FIXME: I get different results for the same data w. different
+ # compilers. Bug in LittleCMS or in the binding?
+ if profile.profile.is_intent_supported(intent, direction):
+ return 1
+ else:
+ return -1
+ except (AttributeError, IOError, TypeError, ValueError) as v:
+ raise PyCMSError(v)
+
+
+def versions():
+ """
+ (pyCMS) Fetches versions.
+ """
+
+ import sys
+ return (
+ VERSION, core.littlecms_version,
+ sys.version.split()[0], Image.VERSION
+ )
+
+# --------------------------------------------------------------------
+
+if __name__ == "__main__":
+ # create a cheap manual from the __doc__ strings for the functions above
+
+ from PIL import ImageCms
+ print(__doc__)
+
+ for f in dir(ImageCms):
+ doc = None
+ try:
+ exec("doc = %s.__doc__" % (f))
+ if "pyCMS" in doc:
+ # so we don't get the __doc__ string for imported modules
+ print("=" * 80)
+ print("%s" % f)
+ print(doc)
+ except (AttributeError, TypeError):
+ pass
+
+# End of file
diff --git a/lib/Python/Lib/PIL/ImageColor.py b/lib/Python/Lib/PIL/ImageColor.py
new file mode 100644
index 000000000..fc95e6d19
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageColor.py
@@ -0,0 +1,279 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# map CSS3-style colour description strings to RGB
+#
+# History:
+# 2002-10-24 fl Added support for CSS-style color strings
+# 2002-12-15 fl Added RGBA support
+# 2004-03-27 fl Fixed remaining int() problems for Python 1.5.2
+# 2004-07-19 fl Fixed gray/grey spelling issues
+# 2009-03-05 fl Fixed rounding error in grayscale calculation
+#
+# Copyright (c) 2002-2004 by Secret Labs AB
+# Copyright (c) 2002-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+import re
+
+
+def getrgb(color):
+ """
+ Convert a color string to an RGB tuple. If the string cannot be parsed,
+ this function raises a :py:exc:`ValueError` exception.
+
+ .. versionadded:: 1.1.4
+
+ :param color: A color string
+ :return: ``(red, green, blue[, alpha])``
+ """
+ try:
+ rgb = colormap[color]
+ except KeyError:
+ try:
+ # fall back on case-insensitive lookup
+ rgb = colormap[color.lower()]
+ except KeyError:
+ rgb = None
+ # found color in cache
+ if rgb:
+ if isinstance(rgb, tuple):
+ return rgb
+ colormap[color] = rgb = getrgb(rgb)
+ return rgb
+ # check for known string formats
+ m = re.match("#\w\w\w$", color)
+ if m:
+ return (
+ int(color[1]*2, 16),
+ int(color[2]*2, 16),
+ int(color[3]*2, 16)
+ )
+ m = re.match("#\w\w\w\w\w\w$", color)
+ if m:
+ return (
+ int(color[1:3], 16),
+ int(color[3:5], 16),
+ int(color[5:7], 16)
+ )
+ m = re.match("rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
+ if m:
+ return (
+ int(m.group(1)),
+ int(m.group(2)),
+ int(m.group(3))
+ )
+ m = re.match("rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
+ if m:
+ return (
+ int((int(m.group(1)) * 255) / 100.0 + 0.5),
+ int((int(m.group(2)) * 255) / 100.0 + 0.5),
+ int((int(m.group(3)) * 255) / 100.0 + 0.5)
+ )
+ m = re.match("hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
+ if m:
+ from colorsys import hls_to_rgb
+ rgb = hls_to_rgb(
+ float(m.group(1)) / 360.0,
+ float(m.group(3)) / 100.0,
+ float(m.group(2)) / 100.0,
+ )
+ return (
+ int(rgb[0] * 255 + 0.5),
+ int(rgb[1] * 255 + 0.5),
+ int(rgb[2] * 255 + 0.5)
+ )
+ m = re.match("rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$",
+ color)
+ if m:
+ return (
+ int(m.group(1)),
+ int(m.group(2)),
+ int(m.group(3)),
+ int(m.group(4))
+ )
+ raise ValueError("unknown color specifier: %r" % color)
+
+
+def getcolor(color, mode):
+ """
+ Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
+ greyscale value if the mode is not color or a palette image. If the string
+ cannot be parsed, this function raises a :py:exc:`ValueError` exception.
+
+ .. versionadded:: 1.1.4
+
+ :param color: A color string
+ :return: ``(graylevel [, alpha]) or (red, green, blue[, alpha])``
+ """
+ # same as getrgb, but converts the result to the given mode
+ color, alpha = getrgb(color), 255
+ if len(color) == 4:
+ color, alpha = color[0:3], color[3]
+
+ if Image.getmodebase(mode) == "L":
+ r, g, b = color
+ color = (r*299 + g*587 + b*114)//1000
+ if mode[-1] == 'A':
+ return (color, alpha)
+ else:
+ if mode[-1] == 'A':
+ return color + (alpha,)
+ return color
+
+colormap = {
+ # X11 colour table (from "CSS3 module: Color working draft"), with
+ # gray/grey spelling issues fixed. This is a superset of HTML 4.0
+ # colour names used in CSS 1.
+ "aliceblue": "#f0f8ff",
+ "antiquewhite": "#faebd7",
+ "aqua": "#00ffff",
+ "aquamarine": "#7fffd4",
+ "azure": "#f0ffff",
+ "beige": "#f5f5dc",
+ "bisque": "#ffe4c4",
+ "black": "#000000",
+ "blanchedalmond": "#ffebcd",
+ "blue": "#0000ff",
+ "blueviolet": "#8a2be2",
+ "brown": "#a52a2a",
+ "burlywood": "#deb887",
+ "cadetblue": "#5f9ea0",
+ "chartreuse": "#7fff00",
+ "chocolate": "#d2691e",
+ "coral": "#ff7f50",
+ "cornflowerblue": "#6495ed",
+ "cornsilk": "#fff8dc",
+ "crimson": "#dc143c",
+ "cyan": "#00ffff",
+ "darkblue": "#00008b",
+ "darkcyan": "#008b8b",
+ "darkgoldenrod": "#b8860b",
+ "darkgray": "#a9a9a9",
+ "darkgrey": "#a9a9a9",
+ "darkgreen": "#006400",
+ "darkkhaki": "#bdb76b",
+ "darkmagenta": "#8b008b",
+ "darkolivegreen": "#556b2f",
+ "darkorange": "#ff8c00",
+ "darkorchid": "#9932cc",
+ "darkred": "#8b0000",
+ "darksalmon": "#e9967a",
+ "darkseagreen": "#8fbc8f",
+ "darkslateblue": "#483d8b",
+ "darkslategray": "#2f4f4f",
+ "darkslategrey": "#2f4f4f",
+ "darkturquoise": "#00ced1",
+ "darkviolet": "#9400d3",
+ "deeppink": "#ff1493",
+ "deepskyblue": "#00bfff",
+ "dimgray": "#696969",
+ "dimgrey": "#696969",
+ "dodgerblue": "#1e90ff",
+ "firebrick": "#b22222",
+ "floralwhite": "#fffaf0",
+ "forestgreen": "#228b22",
+ "fuchsia": "#ff00ff",
+ "gainsboro": "#dcdcdc",
+ "ghostwhite": "#f8f8ff",
+ "gold": "#ffd700",
+ "goldenrod": "#daa520",
+ "gray": "#808080",
+ "grey": "#808080",
+ "green": "#008000",
+ "greenyellow": "#adff2f",
+ "honeydew": "#f0fff0",
+ "hotpink": "#ff69b4",
+ "indianred": "#cd5c5c",
+ "indigo": "#4b0082",
+ "ivory": "#fffff0",
+ "khaki": "#f0e68c",
+ "lavender": "#e6e6fa",
+ "lavenderblush": "#fff0f5",
+ "lawngreen": "#7cfc00",
+ "lemonchiffon": "#fffacd",
+ "lightblue": "#add8e6",
+ "lightcoral": "#f08080",
+ "lightcyan": "#e0ffff",
+ "lightgoldenrodyellow": "#fafad2",
+ "lightgreen": "#90ee90",
+ "lightgray": "#d3d3d3",
+ "lightgrey": "#d3d3d3",
+ "lightpink": "#ffb6c1",
+ "lightsalmon": "#ffa07a",
+ "lightseagreen": "#20b2aa",
+ "lightskyblue": "#87cefa",
+ "lightslategray": "#778899",
+ "lightslategrey": "#778899",
+ "lightsteelblue": "#b0c4de",
+ "lightyellow": "#ffffe0",
+ "lime": "#00ff00",
+ "limegreen": "#32cd32",
+ "linen": "#faf0e6",
+ "magenta": "#ff00ff",
+ "maroon": "#800000",
+ "mediumaquamarine": "#66cdaa",
+ "mediumblue": "#0000cd",
+ "mediumorchid": "#ba55d3",
+ "mediumpurple": "#9370db",
+ "mediumseagreen": "#3cb371",
+ "mediumslateblue": "#7b68ee",
+ "mediumspringgreen": "#00fa9a",
+ "mediumturquoise": "#48d1cc",
+ "mediumvioletred": "#c71585",
+ "midnightblue": "#191970",
+ "mintcream": "#f5fffa",
+ "mistyrose": "#ffe4e1",
+ "moccasin": "#ffe4b5",
+ "navajowhite": "#ffdead",
+ "navy": "#000080",
+ "oldlace": "#fdf5e6",
+ "olive": "#808000",
+ "olivedrab": "#6b8e23",
+ "orange": "#ffa500",
+ "orangered": "#ff4500",
+ "orchid": "#da70d6",
+ "palegoldenrod": "#eee8aa",
+ "palegreen": "#98fb98",
+ "paleturquoise": "#afeeee",
+ "palevioletred": "#db7093",
+ "papayawhip": "#ffefd5",
+ "peachpuff": "#ffdab9",
+ "peru": "#cd853f",
+ "pink": "#ffc0cb",
+ "plum": "#dda0dd",
+ "powderblue": "#b0e0e6",
+ "purple": "#800080",
+ "red": "#ff0000",
+ "rosybrown": "#bc8f8f",
+ "royalblue": "#4169e1",
+ "saddlebrown": "#8b4513",
+ "salmon": "#fa8072",
+ "sandybrown": "#f4a460",
+ "seagreen": "#2e8b57",
+ "seashell": "#fff5ee",
+ "sienna": "#a0522d",
+ "silver": "#c0c0c0",
+ "skyblue": "#87ceeb",
+ "slateblue": "#6a5acd",
+ "slategray": "#708090",
+ "slategrey": "#708090",
+ "snow": "#fffafa",
+ "springgreen": "#00ff7f",
+ "steelblue": "#4682b4",
+ "tan": "#d2b48c",
+ "teal": "#008080",
+ "thistle": "#d8bfd8",
+ "tomato": "#ff6347",
+ "turquoise": "#40e0d0",
+ "violet": "#ee82ee",
+ "wheat": "#f5deb3",
+ "white": "#ffffff",
+ "whitesmoke": "#f5f5f5",
+ "yellow": "#ffff00",
+ "yellowgreen": "#9acd32",
+}
diff --git a/lib/Python/Lib/PIL/ImageDraw.py b/lib/Python/Lib/PIL/ImageDraw.py
new file mode 100644
index 000000000..a2a75d1c6
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageDraw.py
@@ -0,0 +1,383 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# drawing interface operations
+#
+# History:
+# 1996-04-13 fl Created (experimental)
+# 1996-08-07 fl Filled polygons, ellipses.
+# 1996-08-13 fl Added text support
+# 1998-06-28 fl Handle I and F images
+# 1998-12-29 fl Added arc; use arc primitive to draw ellipses
+# 1999-01-10 fl Added shape stuff (experimental)
+# 1999-02-06 fl Added bitmap support
+# 1999-02-11 fl Changed all primitives to take options
+# 1999-02-20 fl Fixed backwards compatibility
+# 2000-10-12 fl Copy on write, when necessary
+# 2001-02-18 fl Use default ink for bitmap/text also in fill mode
+# 2002-10-24 fl Added support for CSS-style color strings
+# 2002-12-10 fl Added experimental support for RGBA-on-RGB drawing
+# 2002-12-11 fl Refactored low-level drawing API (work in progress)
+# 2004-08-26 fl Made Draw() a factory function, added getdraw() support
+# 2004-09-04 fl Added width support to line primitive
+# 2004-09-10 fl Added font mode handling
+# 2006-06-19 fl Added font bearing support (getmask2)
+#
+# Copyright (c) 1997-2006 by Secret Labs AB
+# Copyright (c) 1996-2006 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import numbers
+
+from PIL import Image, ImageColor
+from PIL._util import isStringType
+
+try:
+ import warnings
+except ImportError:
+ warnings = None
+
+
+##
+# A simple 2D drawing interface for PIL images.
+# <p>
+# Application code should use the <b>Draw</b> factory, instead of
+# directly.
+
+class ImageDraw:
+
+ ##
+ # Create a drawing instance.
+ #
+ # @param im The image to draw in.
+ # @param mode Optional mode to use for color values. For RGB
+ # images, this argument can be RGB or RGBA (to blend the
+ # drawing into the image). For all other modes, this argument
+ # must be the same as the image mode. If omitted, the mode
+ # defaults to the mode of the image.
+
+ def __init__(self, im, mode=None):
+ im.load()
+ if im.readonly:
+ im._copy() # make it writeable
+ blend = 0
+ if mode is None:
+ mode = im.mode
+ if mode != im.mode:
+ if mode == "RGBA" and im.mode == "RGB":
+ blend = 1
+ else:
+ raise ValueError("mode mismatch")
+ if mode == "P":
+ self.palette = im.palette
+ else:
+ self.palette = None
+ self.im = im.im
+ self.draw = Image.core.draw(self.im, blend)
+ self.mode = mode
+ if mode in ("I", "F"):
+ self.ink = self.draw.draw_ink(1, mode)
+ else:
+ self.ink = self.draw.draw_ink(-1, mode)
+ if mode in ("1", "P", "I", "F"):
+ # FIXME: fix Fill2 to properly support matte for I+F images
+ self.fontmode = "1"
+ else:
+ self.fontmode = "L" # aliasing is okay for other modes
+ self.fill = 0
+ self.font = None
+
+ ##
+ # Set the default pen color.
+
+ def setink(self, ink):
+ # compatibility
+ if warnings:
+ warnings.warn(
+ "'setink' is deprecated; use keyword arguments instead",
+ DeprecationWarning, stacklevel=2
+ )
+ if isStringType(ink):
+ ink = ImageColor.getcolor(ink, self.mode)
+ if self.palette and not isinstance(ink, numbers.Number):
+ ink = self.palette.getcolor(ink)
+ self.ink = self.draw.draw_ink(ink, self.mode)
+
+ ##
+ # Set the default background color.
+
+ def setfill(self, onoff):
+ # compatibility
+ if warnings:
+ warnings.warn(
+ "'setfill' is deprecated; use keyword arguments instead",
+ DeprecationWarning, stacklevel=2
+ )
+ self.fill = onoff
+
+ ##
+ # Set the default font.
+
+ def setfont(self, font):
+ # compatibility
+ self.font = font
+
+ ##
+ # Get the current default font.
+
+ def getfont(self):
+ if not self.font:
+ # FIXME: should add a font repository
+ from PIL import ImageFont
+ self.font = ImageFont.load_default()
+ return self.font
+
+ def _getink(self, ink, fill=None):
+ if ink is None and fill is None:
+ if self.fill:
+ fill = self.ink
+ else:
+ ink = self.ink
+ else:
+ if ink is not None:
+ if isStringType(ink):
+ ink = ImageColor.getcolor(ink, self.mode)
+ if self.palette and not isinstance(ink, numbers.Number):
+ ink = self.palette.getcolor(ink)
+ ink = self.draw.draw_ink(ink, self.mode)
+ if fill is not None:
+ if isStringType(fill):
+ fill = ImageColor.getcolor(fill, self.mode)
+ if self.palette and not isinstance(fill, numbers.Number):
+ fill = self.palette.getcolor(fill)
+ fill = self.draw.draw_ink(fill, self.mode)
+ return ink, fill
+
+ ##
+ # Draw an arc.
+
+ def arc(self, xy, start, end, fill=None):
+ ink, fill = self._getink(fill)
+ if ink is not None:
+ self.draw.draw_arc(xy, start, end, ink)
+
+ ##
+ # Draw a bitmap.
+
+ def bitmap(self, xy, bitmap, fill=None):
+ bitmap.load()
+ ink, fill = self._getink(fill)
+ if ink is None:
+ ink = fill
+ if ink is not None:
+ self.draw.draw_bitmap(xy, bitmap.im, ink)
+
+ ##
+ # Draw a chord.
+
+ def chord(self, xy, start, end, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_chord(xy, start, end, fill, 1)
+ if ink is not None:
+ self.draw.draw_chord(xy, start, end, ink, 0)
+
+ ##
+ # Draw an ellipse.
+
+ def ellipse(self, xy, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_ellipse(xy, fill, 1)
+ if ink is not None:
+ self.draw.draw_ellipse(xy, ink, 0)
+
+ ##
+ # Draw a line, or a connected sequence of line segments.
+
+ def line(self, xy, fill=None, width=0):
+ ink, fill = self._getink(fill)
+ if ink is not None:
+ self.draw.draw_lines(xy, ink, width)
+
+ ##
+ # (Experimental) Draw a shape.
+
+ def shape(self, shape, fill=None, outline=None):
+ # experimental
+ shape.close()
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_outline(shape, fill, 1)
+ if ink is not None:
+ self.draw.draw_outline(shape, ink, 0)
+
+ ##
+ # Draw a pieslice.
+
+ def pieslice(self, xy, start, end, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_pieslice(xy, start, end, fill, 1)
+ if ink is not None:
+ self.draw.draw_pieslice(xy, start, end, ink, 0)
+
+ ##
+ # Draw one or more individual pixels.
+
+ def point(self, xy, fill=None):
+ ink, fill = self._getink(fill)
+ if ink is not None:
+ self.draw.draw_points(xy, ink)
+
+ ##
+ # Draw a polygon.
+
+ def polygon(self, xy, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_polygon(xy, fill, 1)
+ if ink is not None:
+ self.draw.draw_polygon(xy, ink, 0)
+
+ ##
+ # Draw a rectangle.
+
+ def rectangle(self, xy, fill=None, outline=None):
+ ink, fill = self._getink(outline, fill)
+ if fill is not None:
+ self.draw.draw_rectangle(xy, fill, 1)
+ if ink is not None:
+ self.draw.draw_rectangle(xy, ink, 0)
+
+ ##
+ # Draw text.
+
+ def text(self, xy, text, fill=None, font=None, anchor=None):
+ ink, fill = self._getink(fill)
+ if font is None:
+ font = self.getfont()
+ if ink is None:
+ ink = fill
+ if ink is not None:
+ try:
+ mask, offset = font.getmask2(text, self.fontmode)
+ xy = xy[0] + offset[0], xy[1] + offset[1]
+ except AttributeError:
+ try:
+ mask = font.getmask(text, self.fontmode)
+ except TypeError:
+ mask = font.getmask(text)
+ self.draw.draw_bitmap(xy, mask, ink)
+
+ ##
+ # Get the size of a given string, in pixels.
+
+ def textsize(self, text, font=None):
+ if font is None:
+ font = self.getfont()
+ return font.getsize(text)
+
+
+##
+# A simple 2D drawing interface for PIL images.
+#
+# @param im The image to draw in.
+# @param mode Optional mode to use for color values. For RGB
+# images, this argument can be RGB or RGBA (to blend the
+# drawing into the image). For all other modes, this argument
+# must be the same as the image mode. If omitted, the mode
+# defaults to the mode of the image.
+
+def Draw(im, mode=None):
+ try:
+ return im.getdraw(mode)
+ except AttributeError:
+ return ImageDraw(im, mode)
+
+# experimental access to the outline API
+try:
+ Outline = Image.core.outline
+except:
+ Outline = None
+
+
+##
+# (Experimental) A more advanced 2D drawing interface for PIL images,
+# based on the WCK interface.
+#
+# @param im The image to draw in.
+# @param hints An optional list of hints.
+# @return A (drawing context, drawing resource factory) tuple.
+
+def getdraw(im=None, hints=None):
+ # FIXME: this needs more work!
+ # FIXME: come up with a better 'hints' scheme.
+ handler = None
+ if not hints or "nicest" in hints:
+ try:
+ from PIL import _imagingagg as handler
+ except ImportError:
+ pass
+ if handler is None:
+ from PIL import ImageDraw2 as handler
+ if im:
+ im = handler.Draw(im)
+ return im, handler
+
+
+##
+# (experimental) Fills a bounded region with a given color.
+#
+# @param image Target image.
+# @param xy Seed position (a 2-item coordinate tuple).
+# @param value Fill color.
+# @param border Optional border value. If given, the region consists of
+# pixels with a color different from the border color. If not given,
+# the region consists of pixels having the same color as the seed
+# pixel.
+
+def floodfill(image, xy, value, border=None):
+ "Fill bounded region."
+ # based on an implementation by Eric S. Raymond
+ pixel = image.load()
+ x, y = xy
+ try:
+ background = pixel[x, y]
+ if background == value:
+ return # seed point already has fill color
+ pixel[x, y] = value
+ except IndexError:
+ return # seed point outside image
+ edge = [(x, y)]
+ if border is None:
+ while edge:
+ newedge = []
+ for (x, y) in edge:
+ for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
+ try:
+ p = pixel[s, t]
+ except IndexError:
+ pass
+ else:
+ if p == background:
+ pixel[s, t] = value
+ newedge.append((s, t))
+ edge = newedge
+ else:
+ while edge:
+ newedge = []
+ for (x, y) in edge:
+ for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
+ try:
+ p = pixel[s, t]
+ except IndexError:
+ pass
+ else:
+ if p != value and p != border:
+ pixel[s, t] = value
+ newedge.append((s, t))
+ edge = newedge
diff --git a/lib/Python/Lib/PIL/ImageDraw2.py b/lib/Python/Lib/PIL/ImageDraw2.py
new file mode 100644
index 000000000..c967a200f
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageDraw2.py
@@ -0,0 +1,111 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# WCK-style drawing interface operations
+#
+# History:
+# 2003-12-07 fl created
+# 2005-05-15 fl updated; added to PIL as ImageDraw2
+# 2005-05-15 fl added text support
+# 2005-05-20 fl added arc/chord/pieslice support
+#
+# Copyright (c) 2003-2005 by Secret Labs AB
+# Copyright (c) 2003-2005 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePath
+
+
+class Pen:
+ def __init__(self, color, width=1, opacity=255):
+ self.color = ImageColor.getrgb(color)
+ self.width = width
+
+
+class Brush:
+ def __init__(self, color, opacity=255):
+ self.color = ImageColor.getrgb(color)
+
+
+class Font:
+ def __init__(self, color, file, size=12):
+ # FIXME: add support for bitmap fonts
+ self.color = ImageColor.getrgb(color)
+ self.font = ImageFont.truetype(file, size)
+
+
+class Draw:
+
+ def __init__(self, image, size=None, color=None):
+ if not hasattr(image, "im"):
+ image = Image.new(image, size, color)
+ self.draw = ImageDraw.Draw(image)
+ self.image = image
+ self.transform = None
+
+ def flush(self):
+ return self.image
+
+ def render(self, op, xy, pen, brush=None):
+ # handle color arguments
+ outline = fill = None
+ width = 1
+ if isinstance(pen, Pen):
+ outline = pen.color
+ width = pen.width
+ elif isinstance(brush, Pen):
+ outline = brush.color
+ width = brush.width
+ if isinstance(brush, Brush):
+ fill = brush.color
+ elif isinstance(pen, Brush):
+ fill = pen.color
+ # handle transformation
+ if self.transform:
+ xy = ImagePath.Path(xy)
+ xy.transform(self.transform)
+ # render the item
+ if op == "line":
+ self.draw.line(xy, fill=outline, width=width)
+ else:
+ getattr(self.draw, op)(xy, fill=fill, outline=outline)
+
+ def settransform(self, offset):
+ (xoffset, yoffset) = offset
+ self.transform = (1, 0, xoffset, 0, 1, yoffset)
+
+ def arc(self, xy, start, end, *options):
+ self.render("arc", xy, start, end, *options)
+
+ def chord(self, xy, start, end, *options):
+ self.render("chord", xy, start, end, *options)
+
+ def ellipse(self, xy, *options):
+ self.render("ellipse", xy, *options)
+
+ def line(self, xy, *options):
+ self.render("line", xy, *options)
+
+ def pieslice(self, xy, start, end, *options):
+ self.render("pieslice", xy, start, end, *options)
+
+ def polygon(self, xy, *options):
+ self.render("polygon", xy, *options)
+
+ def rectangle(self, xy, *options):
+ self.render("rectangle", xy, *options)
+
+ def symbol(self, xy, symbol, *options):
+ raise NotImplementedError("not in this version")
+
+ def text(self, xy, text, font):
+ if self.transform:
+ xy = ImagePath.Path(xy)
+ xy.transform(self.transform)
+ self.draw.text(xy, text, font=font.font, fill=font.color)
+
+ def textsize(self, text, font):
+ return self.draw.textsize(text, font=font.font)
diff --git a/lib/Python/Lib/PIL/ImageEnhance.py b/lib/Python/Lib/PIL/ImageEnhance.py
new file mode 100644
index 000000000..a196d5b09
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageEnhance.py
@@ -0,0 +1,99 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# image enhancement classes
+#
+# For a background, see "Image Processing By Interpolation and
+# Extrapolation", Paul Haeberli and Douglas Voorhies. Available
+# at http://www.graficaobscura.com/interp/index.html
+#
+# History:
+# 1996-03-23 fl Created
+# 2009-06-16 fl Fixed mean calculation
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image, ImageFilter, ImageStat
+
+
+class _Enhance:
+
+ def enhance(self, factor):
+ """
+ Returns an enhanced image.
+
+ :param factor: A floating point value controlling the enhancement.
+ Factor 1.0 always returns a copy of the original image,
+ lower factors mean less color (brightness, contrast,
+ etc), and higher values more. There are no restrictions
+ on this value.
+ :rtype: :py:class:`~PIL.Image.Image`
+ """
+ return Image.blend(self.degenerate, self.image, factor)
+
+
+class Color(_Enhance):
+ """Adjust image color balance.
+
+ This class can be used to adjust the colour balance of an image, in
+ a manner similar to the controls on a colour TV set. An enhancement
+ factor of 0.0 gives a black and white image. A factor of 1.0 gives
+ the original image.
+ """
+ def __init__(self, image):
+ self.image = image
+ self.intermediate_mode = 'L'
+ if 'A' in image.getbands():
+ self.intermediate_mode = 'LA'
+
+ self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
+
+class Contrast(_Enhance):
+ """Adjust image contrast.
+
+ This class can be used to control the contrast of an image, similar
+ to the contrast control on a TV set. An enhancement factor of 0.0
+ gives a solid grey image. A factor of 1.0 gives the original image.
+ """
+ def __init__(self, image):
+ self.image = image
+ mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
+ self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
+
+ if 'A' in image.getbands():
+ self.degenerate.putalpha(image.split()[-1])
+
+
+class Brightness(_Enhance):
+ """Adjust image brightness.
+
+ This class can be used to control the brighntess of an image. An
+ enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
+ original image.
+ """
+ def __init__(self, image):
+ self.image = image
+ self.degenerate = Image.new(image.mode, image.size, 0)
+
+ if 'A' in image.getbands():
+ self.degenerate.putalpha(image.split()[-1])
+
+
+class Sharpness(_Enhance):
+ """Adjust image sharpness.
+
+ This class can be used to adjust the sharpness of an image. An
+ enhancement factor of 0.0 gives a blurred image, a factor of 1.0 gives the
+ original image, and a factor of 2.0 gives a sharpened image.
+ """
+ def __init__(self, image):
+ self.image = image
+ self.degenerate = image.filter(ImageFilter.SMOOTH)
+
+ if 'A' in image.getbands():
+ self.degenerate.putalpha(image.split()[-1])
diff --git a/lib/Python/Lib/PIL/ImageFile.py b/lib/Python/Lib/PIL/ImageFile.py
new file mode 100644
index 000000000..79faff797
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageFile.py
@@ -0,0 +1,526 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# base class for image file handlers
+#
+# history:
+# 1995-09-09 fl Created
+# 1996-03-11 fl Fixed load mechanism.
+# 1996-04-15 fl Added pcx/xbm decoders.
+# 1996-04-30 fl Added encoders.
+# 1996-12-14 fl Added load helpers
+# 1997-01-11 fl Use encode_to_file where possible
+# 1997-08-27 fl Flush output in _save
+# 1998-03-05 fl Use memory mapping for some modes
+# 1999-02-04 fl Use memory mapping also for "I;16" and "I;16B"
+# 1999-05-31 fl Added image parser
+# 2000-10-12 fl Set readonly flag on memory-mapped images
+# 2002-03-20 fl Use better messages for common decoder errors
+# 2003-04-21 fl Fall back on mmap/map_buffer if map is not available
+# 2003-10-30 fl Added StubImageFile class
+# 2004-02-25 fl Made incremental parser more robust
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1995-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+from PIL._util import isPath
+import io
+import os
+import sys
+import traceback
+
+MAXBLOCK = 65536
+
+SAFEBLOCK = 1024*1024
+
+LOAD_TRUNCATED_IMAGES = False
+
+ERRORS = {
+ -1: "image buffer overrun error",
+ -2: "decoding error",
+ -3: "unknown error",
+ -8: "bad configuration",
+ -9: "out of memory error"
+}
+
+
+def raise_ioerror(error):
+ try:
+ message = Image.core.getcodecstatus(error)
+ except AttributeError:
+ message = ERRORS.get(error)
+ if not message:
+ message = "decoder error %d" % error
+ raise IOError(message + " when reading image file")
+
+
+#
+# --------------------------------------------------------------------
+# Helpers
+
+def _tilesort(t):
+ # sort on offset
+ return t[2]
+
+
+#
+# --------------------------------------------------------------------
+# ImageFile base class
+
+class ImageFile(Image.Image):
+ "Base class for image file format handlers."
+
+ def __init__(self, fp=None, filename=None):
+ Image.Image.__init__(self)
+
+ self.tile = None
+ self.readonly = 1 # until we know better
+
+ self.decoderconfig = ()
+ self.decodermaxblock = MAXBLOCK
+
+ if isPath(fp):
+ # filename
+ self.fp = open(fp, "rb")
+ self.filename = fp
+ else:
+ # stream
+ self.fp = fp
+ self.filename = filename
+
+ try:
+ self._open()
+ except IndexError as v: # end of data
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError(v)
+ except TypeError as v: # end of data (ord)
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError(v)
+ except KeyError as v: # unsupported mode
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError(v)
+ except EOFError as v: # got header but not the first frame
+ if Image.DEBUG > 1:
+ traceback.print_exc()
+ raise SyntaxError(v)
+
+ if not self.mode or self.size[0] <= 0:
+ raise SyntaxError("not identified by this driver")
+
+ def draft(self, mode, size):
+ "Set draft mode"
+
+ pass
+
+ def verify(self):
+ "Check file integrity"
+
+ # raise exception if something's wrong. must be called
+ # directly after open, and closes file when finished.
+ self.fp = None
+
+ def load(self):
+ "Load image data based on tile list"
+
+ pixel = Image.Image.load(self)
+
+ if self.tile is None:
+ raise IOError("cannot load this image")
+ if not self.tile:
+ return pixel
+
+ self.map = None
+ use_mmap = self.filename and len(self.tile) == 1
+ # As of pypy 2.1.0, memory mapping was failing here.
+ use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info')
+
+ readonly = 0
+
+ # look for read/seek overrides
+ try:
+ read = self.load_read
+ # don't use mmap if there are custom read/seek functions
+ use_mmap = False
+ except AttributeError:
+ read = self.fp.read
+
+ try:
+ seek = self.load_seek
+ use_mmap = False
+ except AttributeError:
+ seek = self.fp.seek
+
+ if use_mmap:
+ # try memory mapping
+ d, e, o, a = self.tile[0]
+ if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES:
+ try:
+ if hasattr(Image.core, "map"):
+ # use built-in mapper
+ self.map = Image.core.map(self.filename)
+ self.map.seek(o)
+ self.im = self.map.readimage(
+ self.mode, self.size, a[1], a[2]
+ )
+ else:
+ # use mmap, if possible
+ import mmap
+ file = open(self.filename, "r+")
+ size = os.path.getsize(self.filename)
+ # FIXME: on Unix, use PROT_READ etc
+ self.map = mmap.mmap(file.fileno(), size)
+ self.im = Image.core.map_buffer(
+ self.map, self.size, d, e, o, a
+ )
+ readonly = 1
+ except (AttributeError, EnvironmentError, ImportError):
+ self.map = None
+
+ self.load_prepare()
+
+ if not self.map:
+ # sort tiles in file order
+ self.tile.sort(key=_tilesort)
+
+ try:
+ # FIXME: This is a hack to handle TIFF's JpegTables tag.
+ prefix = self.tile_prefix
+ except AttributeError:
+ prefix = b""
+
+ # Buffer length read; assign a default value
+ t = 0
+
+ for d, e, o, a in self.tile:
+ d = Image._getdecoder(self.mode, d, a, self.decoderconfig)
+ seek(o)
+ try:
+ d.setimage(self.im, e)
+ except ValueError:
+ continue
+ b = prefix
+ t = len(b)
+ while True:
+ try:
+ s = read(self.decodermaxblock)
+ except IndexError as ie: # truncated png/gif
+ if LOAD_TRUNCATED_IMAGES:
+ break
+ else:
+ raise IndexError(ie)
+
+ if not s and not d.handles_eof: # truncated jpeg
+ self.tile = []
+
+ # JpegDecode needs to clean things up here either way
+ # If we don't destroy the decompressor,
+ # we have a memory leak.
+ d.cleanup()
+
+ if LOAD_TRUNCATED_IMAGES:
+ break
+ else:
+ raise IOError("image file is truncated "
+ "(%d bytes not processed)" % len(b))
+
+ b = b + s
+ n, e = d.decode(b)
+ if n < 0:
+ break
+ b = b[n:]
+ t = t + n
+ # Need to cleanup here to prevent leaks in PyPy
+ d.cleanup()
+
+ self.tile = []
+ self.readonly = readonly
+
+ self.fp = None # might be shared
+
+ if not self.map and (not LOAD_TRUNCATED_IMAGES or t == 0) and e < 0:
+ # still raised if decoder fails to return anything
+ raise_ioerror(e)
+
+ # post processing
+ if hasattr(self, "tile_post_rotate"):
+ # FIXME: This is a hack to handle rotated PCD's
+ self.im = self.im.rotate(self.tile_post_rotate)
+ self.size = self.im.size
+
+ self.load_end()
+
+ return Image.Image.load(self)
+
+ def load_prepare(self):
+ # create image memory if necessary
+ if not self.im or\
+ self.im.mode != self.mode or self.im.size != self.size:
+ self.im = Image.core.new(self.mode, self.size)
+ # create palette (optional)
+ if self.mode == "P":
+ Image.Image.load(self)
+
+ def load_end(self):
+ # may be overridden
+ pass
+
+ # may be defined for contained formats
+ # def load_seek(self, pos):
+ # pass
+
+ # may be defined for blocked formats (e.g. PNG)
+ # def load_read(self, bytes):
+ # pass
+
+
+class StubImageFile(ImageFile):
+ """
+ Base class for stub image loaders.
+
+ A stub loader is an image loader that can identify files of a
+ certain format, but relies on external code to load the file.
+ """
+
+ def _open(self):
+ raise NotImplementedError(
+ "StubImageFile subclass must implement _open"
+ )
+
+ def load(self):
+ loader = self._load()
+ if loader is None:
+ raise IOError("cannot find loader for this %s file" % self.format)
+ image = loader.load(self)
+ assert image is not None
+ # become the other object (!)
+ self.__class__ = image.__class__
+ self.__dict__ = image.__dict__
+
+ def _load(self):
+ "(Hook) Find actual image loader."
+ raise NotImplementedError(
+ "StubImageFile subclass must implement _load"
+ )
+
+
+class Parser:
+ """
+ Incremental image parser. This class implements the standard
+ feed/close consumer interface.
+
+ In Python 2.x, this is an old-style class.
+ """
+ incremental = None
+ image = None
+ data = None
+ decoder = None
+ finished = 0
+
+ def reset(self):
+ """
+ (Consumer) Reset the parser. Note that you can only call this
+ method immediately after you've created a parser; parser
+ instances cannot be reused.
+ """
+ assert self.data is None, "cannot reuse parsers"
+
+ def feed(self, data):
+ """
+ (Consumer) Feed data to the parser.
+
+ :param data: A string buffer.
+ :exception IOError: If the parser failed to parse the image file.
+ """
+ # collect data
+
+ if self.finished:
+ return
+
+ if self.data is None:
+ self.data = data
+ else:
+ self.data = self.data + data
+
+ # parse what we have
+ if self.decoder:
+
+ if self.offset > 0:
+ # skip header
+ skip = min(len(self.data), self.offset)
+ self.data = self.data[skip:]
+ self.offset = self.offset - skip
+ if self.offset > 0 or not self.data:
+ return
+
+ n, e = self.decoder.decode(self.data)
+
+ if n < 0:
+ # end of stream
+ self.data = None
+ self.finished = 1
+ if e < 0:
+ # decoding error
+ self.image = None
+ raise_ioerror(e)
+ else:
+ # end of image
+ return
+ self.data = self.data[n:]
+
+ elif self.image:
+
+ # if we end up here with no decoder, this file cannot
+ # be incrementally parsed. wait until we've gotten all
+ # available data
+ pass
+
+ else:
+
+ # attempt to open this file
+ try:
+ try:
+ fp = io.BytesIO(self.data)
+ im = Image.open(fp)
+ finally:
+ fp.close() # explicitly close the virtual file
+ except IOError:
+ # traceback.print_exc()
+ pass # not enough data
+ else:
+ flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
+ if flag or len(im.tile) != 1:
+ # custom load code, or multiple tiles
+ self.decode = None
+ else:
+ # initialize decoder
+ im.load_prepare()
+ d, e, o, a = im.tile[0]
+ im.tile = []
+ self.decoder = Image._getdecoder(
+ im.mode, d, a, im.decoderconfig
+ )
+ self.decoder.setimage(im.im, e)
+
+ # calculate decoder offset
+ self.offset = o
+ if self.offset <= len(self.data):
+ self.data = self.data[self.offset:]
+ self.offset = 0
+
+ self.image = im
+
+ def close(self):
+ """
+ (Consumer) Close the stream.
+
+ :returns: An image object.
+ :exception IOError: If the parser failed to parse the image file either
+ because it cannot be identified or cannot be
+ decoded.
+ """
+ # finish decoding
+ if self.decoder:
+ # get rid of what's left in the buffers
+ self.feed(b"")
+ self.data = self.decoder = None
+ if not self.finished:
+ raise IOError("image was incomplete")
+ if not self.image:
+ raise IOError("cannot parse this image")
+ if self.data:
+ # incremental parsing not possible; reopen the file
+ # not that we have all data
+ try:
+ fp = io.BytesIO(self.data)
+ self.image = Image.open(fp)
+ finally:
+ self.image.load()
+ fp.close() # explicitly close the virtual file
+ return self.image
+
+
+# --------------------------------------------------------------------
+
+def _save(im, fp, tile, bufsize=0):
+ """Helper to save image based on tile list
+
+ :param im: Image object.
+ :param fp: File object.
+ :param tile: Tile list.
+ :param bufsize: Optional buffer size
+ """
+
+ im.load()
+ if not hasattr(im, "encoderconfig"):
+ im.encoderconfig = ()
+ tile.sort(key=_tilesort)
+ # FIXME: make MAXBLOCK a configuration parameter
+ # It would be great if we could have the encoder specify what it needs
+ # But, it would need at least the image size in most cases. RawEncode is
+ # a tricky case.
+ bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c
+ try:
+ fh = fp.fileno()
+ fp.flush()
+ except (AttributeError, io.UnsupportedOperation):
+ # compress to Python file-compatible object
+ for e, b, o, a in tile:
+ e = Image._getencoder(im.mode, e, a, im.encoderconfig)
+ if o > 0:
+ fp.seek(o, 0)
+ e.setimage(im.im, b)
+ while True:
+ l, s, d = e.encode(bufsize)
+ fp.write(d)
+ if s:
+ break
+ if s < 0:
+ raise IOError("encoder error %d when writing image file" % s)
+ e.cleanup()
+ else:
+ # slight speedup: compress to real file object
+ for e, b, o, a in tile:
+ e = Image._getencoder(im.mode, e, a, im.encoderconfig)
+ if o > 0:
+ fp.seek(o, 0)
+ e.setimage(im.im, b)
+ s = e.encode_to_file(fh, bufsize)
+ if s < 0:
+ raise IOError("encoder error %d when writing image file" % s)
+ e.cleanup()
+ try:
+ fp.flush()
+ except:
+ pass
+
+
+def _safe_read(fp, size):
+ """
+ Reads large blocks in a safe way. Unlike fp.read(n), this function
+ doesn't trust the user. If the requested size is larger than
+ SAFEBLOCK, the file is read block by block.
+
+ :param fp: File handle. Must implement a <b>read</b> method.
+ :param size: Number of bytes to read.
+ :returns: A string containing up to <i>size</i> bytes of data.
+ """
+ if size <= 0:
+ return b""
+ if size <= SAFEBLOCK:
+ return fp.read(size)
+ data = []
+ while size > 0:
+ block = fp.read(min(size, SAFEBLOCK))
+ if not block:
+ break
+ data.append(block)
+ size -= len(block)
+ return b"".join(data)
diff --git a/lib/Python/Lib/PIL/ImageFileIO.py b/lib/Python/Lib/PIL/ImageFileIO.py
new file mode 100644
index 000000000..e57d3f43e
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageFileIO.py
@@ -0,0 +1,40 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# kludge to get basic ImageFileIO functionality
+#
+# History:
+# 1998-08-06 fl Recreated
+#
+# Copyright (c) Secret Labs AB 1998-2002.
+#
+# See the README file for information on usage and redistribution.
+#
+"""
+The **ImageFileIO** module can be used to read an image from a
+socket, or any other stream device.
+
+Deprecated. New code should use the :class:`PIL.ImageFile.Parser`
+class in the :mod:`PIL.ImageFile` module instead.
+
+.. seealso:: modules :class:`PIL.ImageFile.Parser`
+"""
+
+from io import BytesIO
+
+
+class ImageFileIO(BytesIO):
+ def __init__(self, fp):
+ """
+ Adds buffering to a stream file object, in order to
+ provide **seek** and **tell** methods required
+ by the :func:`PIL.Image.Image.open` method. The stream object must
+ implement **read** and **close** methods.
+
+ :param fp: Stream file handle.
+
+ .. seealso:: modules :func:`PIL.Image.open`
+ """
+ data = fp.read()
+ BytesIO.__init__(self, data)
diff --git a/lib/Python/Lib/PIL/ImageFilter.py b/lib/Python/Lib/PIL/ImageFilter.py
new file mode 100644
index 000000000..b46845807
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageFilter.py
@@ -0,0 +1,275 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# standard filters
+#
+# History:
+# 1995-11-27 fl Created
+# 2002-06-08 fl Added rank and mode filters
+# 2003-09-15 fl Fixed rank calculation in rank filter; added expand call
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2002 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from functools import reduce
+
+
+class Filter(object):
+ pass
+
+
+class Kernel(Filter):
+ """
+ Create a convolution kernel. The current version only
+ supports 3x3 and 5x5 integer and floating point kernels.
+
+ In the current version, kernels can only be applied to
+ "L" and "RGB" images.
+
+ :param size: Kernel size, given as (width, height). In the current
+ version, this must be (3,3) or (5,5).
+ :param kernel: A sequence containing kernel weights.
+ :param scale: Scale factor. If given, the result for each pixel is
+ divided by this value. the default is the sum of the
+ kernel weights.
+ :param offset: Offset. If given, this value is added to the result,
+ after it has been divided by the scale factor.
+ """
+
+ def __init__(self, size, kernel, scale=None, offset=0):
+ if scale is None:
+ # default scale is sum of kernel
+ scale = reduce(lambda a, b: a+b, kernel)
+ if size[0] * size[1] != len(kernel):
+ raise ValueError("not enough coefficients in kernel")
+ self.filterargs = size, scale, offset, kernel
+
+ def filter(self, image):
+ if image.mode == "P":
+ raise ValueError("cannot filter palette images")
+ return image.filter(*self.filterargs)
+
+
+class BuiltinFilter(Kernel):
+ def __init__(self):
+ pass
+
+
+class RankFilter(Filter):
+ """
+ Create a rank filter. The rank filter sorts all pixels in
+ a window of the given size, and returns the **rank**'th value.
+
+ :param size: The kernel size, in pixels.
+ :param rank: What pixel value to pick. Use 0 for a min filter,
+ ``size * size / 2`` for a median filter, ``size * size - 1``
+ for a max filter, etc.
+ """
+ name = "Rank"
+
+ def __init__(self, size, rank):
+ self.size = size
+ self.rank = rank
+
+ def filter(self, image):
+ if image.mode == "P":
+ raise ValueError("cannot filter palette images")
+ image = image.expand(self.size//2, self.size//2)
+ return image.rankfilter(self.size, self.rank)
+
+
+class MedianFilter(RankFilter):
+ """
+ Create a median filter. Picks the median pixel value in a window with the
+ given size.
+
+ :param size: The kernel size, in pixels.
+ """
+ name = "Median"
+
+ def __init__(self, size=3):
+ self.size = size
+ self.rank = size*size//2
+
+
+class MinFilter(RankFilter):
+ """
+ Create a min filter. Picks the lowest pixel value in a window with the
+ given size.
+
+ :param size: The kernel size, in pixels.
+ """
+ name = "Min"
+
+ def __init__(self, size=3):
+ self.size = size
+ self.rank = 0
+
+
+class MaxFilter(RankFilter):
+ """
+ Create a max filter. Picks the largest pixel value in a window with the
+ given size.
+
+ :param size: The kernel size, in pixels.
+ """
+ name = "Max"
+
+ def __init__(self, size=3):
+ self.size = size
+ self.rank = size*size-1
+
+
+class ModeFilter(Filter):
+ """
+
+ Create a mode filter. Picks the most frequent pixel value in a box with the
+ given size. Pixel values that occur only once or twice are ignored; if no
+ pixel value occurs more than twice, the original pixel value is preserved.
+
+ :param size: The kernel size, in pixels.
+ """
+ name = "Mode"
+
+ def __init__(self, size=3):
+ self.size = size
+
+ def filter(self, image):
+ return image.modefilter(self.size)
+
+
+class GaussianBlur(Filter):
+ """Gaussian blur filter.
+
+ :param radius: Blur radius.
+ """
+ name = "GaussianBlur"
+
+ def __init__(self, radius=2):
+ self.radius = radius
+
+ def filter(self, image):
+ return image.gaussian_blur(self.radius)
+
+
+class UnsharpMask(Filter):
+ """Unsharp mask filter.
+
+ See Wikipedia's entry on `digital unsharp masking`_ for an explanation of
+ the parameters.
+
+ :param radius: Blur Radius
+ :param percent: Unsharp strength, in percent
+ :param threshold: Threshold controls the minimum brightness change that
+ will be sharpened
+
+ .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
+
+ """
+ name = "UnsharpMask"
+
+ def __init__(self, radius=2, percent=150, threshold=3):
+ self.radius = radius
+ self.percent = percent
+ self.threshold = threshold
+
+ def filter(self, image):
+ return image.unsharp_mask(self.radius, self.percent, self.threshold)
+
+
+class BLUR(BuiltinFilter):
+ name = "Blur"
+ filterargs = (5, 5), 16, 0, (
+ 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1
+ )
+
+
+class CONTOUR(BuiltinFilter):
+ name = "Contour"
+ filterargs = (3, 3), 1, 255, (
+ -1, -1, -1,
+ -1, 8, -1,
+ -1, -1, -1
+ )
+
+
+class DETAIL(BuiltinFilter):
+ name = "Detail"
+ filterargs = (3, 3), 6, 0, (
+ 0, -1, 0,
+ -1, 10, -1,
+ 0, -1, 0
+ )
+
+
+class EDGE_ENHANCE(BuiltinFilter):
+ name = "Edge-enhance"
+ filterargs = (3, 3), 2, 0, (
+ -1, -1, -1,
+ -1, 10, -1,
+ -1, -1, -1
+ )
+
+
+class EDGE_ENHANCE_MORE(BuiltinFilter):
+ name = "Edge-enhance More"
+ filterargs = (3, 3), 1, 0, (
+ -1, -1, -1,
+ -1, 9, -1,
+ -1, -1, -1
+ )
+
+
+class EMBOSS(BuiltinFilter):
+ name = "Emboss"
+ filterargs = (3, 3), 1, 128, (
+ -1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 0
+ )
+
+
+class FIND_EDGES(BuiltinFilter):
+ name = "Find Edges"
+ filterargs = (3, 3), 1, 0, (
+ -1, -1, -1,
+ -1, 8, -1,
+ -1, -1, -1
+ )
+
+
+class SMOOTH(BuiltinFilter):
+ name = "Smooth"
+ filterargs = (3, 3), 13, 0, (
+ 1, 1, 1,
+ 1, 5, 1,
+ 1, 1, 1
+ )
+
+
+class SMOOTH_MORE(BuiltinFilter):
+ name = "Smooth More"
+ filterargs = (5, 5), 100, 0, (
+ 1, 1, 1, 1, 1,
+ 1, 5, 5, 5, 1,
+ 1, 5, 44, 5, 1,
+ 1, 5, 5, 5, 1,
+ 1, 1, 1, 1, 1
+ )
+
+
+class SHARPEN(BuiltinFilter):
+ name = "Sharpen"
+ filterargs = (3, 3), 16, 0, (
+ -2, -2, -2,
+ -2, 32, -2,
+ -2, -2, -2
+ )
diff --git a/lib/Python/Lib/PIL/ImageFont.py b/lib/Python/Lib/PIL/ImageFont.py
new file mode 100644
index 000000000..1e5a27f7b
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageFont.py
@@ -0,0 +1,459 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# PIL raster font management
+#
+# History:
+# 1996-08-07 fl created (experimental)
+# 1997-08-25 fl minor adjustments to handle fonts from pilfont 0.3
+# 1999-02-06 fl rewrote most font management stuff in C
+# 1999-03-17 fl take pth files into account in load_path (from Richard Jones)
+# 2001-02-17 fl added freetype support
+# 2001-05-09 fl added TransposedFont wrapper class
+# 2002-03-04 fl make sure we have a "L" or "1" font
+# 2002-12-04 fl skip non-directory entries in the system path
+# 2003-04-29 fl add embedded default font
+# 2003-09-27 fl added support for truetype charmap encodings
+#
+# Todo:
+# Adapt to PILFONT2 format (16-bit fonts, compressed, single file)
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from __future__ import print_function
+
+from PIL import Image
+from PIL._util import isDirectory, isPath
+import os
+import sys
+
+try:
+ import warnings
+except ImportError:
+ warnings = None
+
+
+class _imagingft_not_installed:
+ # module placeholder
+ def __getattr__(self, id):
+ raise ImportError("The _imagingft C module is not installed")
+
+try:
+ from PIL import _imagingft as core
+except ImportError:
+ core = _imagingft_not_installed()
+
+# FIXME: add support for pilfont2 format (see FontFile.py)
+
+# --------------------------------------------------------------------
+# Font metrics format:
+# "PILfont" LF
+# fontdescriptor LF
+# (optional) key=value... LF
+# "DATA" LF
+# binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox)
+#
+# To place a character, cut out srcbox and paste at dstbox,
+# relative to the character position. Then move the character
+# position according to dx, dy.
+# --------------------------------------------------------------------
+
+
+class ImageFont:
+ "PIL font wrapper"
+
+ def _load_pilfont(self, filename):
+
+ file = open(filename, "rb")
+
+ for ext in (".png", ".gif", ".pbm"):
+ try:
+ fullname = os.path.splitext(filename)[0] + ext
+ image = Image.open(fullname)
+ except:
+ pass
+ else:
+ if image and image.mode in ("1", "L"):
+ break
+ else:
+ raise IOError("cannot find glyph data file")
+
+ self.file = fullname
+
+ return self._load_pilfont_data(file, image)
+
+ def _load_pilfont_data(self, file, image):
+
+ # read PILfont header
+ if file.readline() != b"PILfont\n":
+ raise SyntaxError("Not a PILfont file")
+ file.readline().split(b";")
+ self.info = [] # FIXME: should be a dictionary
+ while True:
+ s = file.readline()
+ if not s or s == b"DATA\n":
+ break
+ self.info.append(s)
+
+ # read PILfont metrics
+ data = file.read(256*20)
+
+ # check image
+ if image.mode not in ("1", "L"):
+ raise TypeError("invalid font image mode")
+
+ image.load()
+
+ self.font = Image.core.font(image.im, data)
+
+ # delegate critical operations to internal type
+ self.getsize = self.font.getsize
+ self.getmask = self.font.getmask
+
+
+##
+# Wrapper for FreeType fonts. Application code should use the
+# <b>truetype</b> factory function to create font objects.
+
+class FreeTypeFont:
+ "FreeType font wrapper (requires _imagingft service)"
+
+ def __init__(self, font=None, size=10, index=0, encoding="", file=None):
+ # FIXME: use service provider instead
+ if file:
+ if warnings:
+ warnings.warn(
+ 'file parameter deprecated, '
+ 'please use font parameter instead.',
+ DeprecationWarning)
+ font = file
+
+ self.path = font
+ self.size = size
+ self.index = index
+ self.encoding = encoding
+
+ if isPath(font):
+ self.font = core.getfont(font, size, index, encoding)
+ else:
+ self.font_bytes = font.read()
+ self.font = core.getfont(
+ "", size, index, encoding, self.font_bytes)
+
+ def getname(self):
+ return self.font.family, self.font.style
+
+ def getmetrics(self):
+ return self.font.ascent, self.font.descent
+
+ def getsize(self, text):
+ size, offset = self.font.getsize(text)
+ return (size[0] + offset[0], size[1] + offset[1])
+
+ def getoffset(self, text):
+ return self.font.getsize(text)[1]
+
+ def getmask(self, text, mode=""):
+ return self.getmask2(text, mode)[0]
+
+ def getmask2(self, text, mode="", fill=Image.core.fill):
+ size, offset = self.font.getsize(text)
+ im = fill("L", size, 0)
+ self.font.render(text, im.id, mode == "1")
+ return im, offset
+
+ def font_variant(self, font=None, size=None, index=None, encoding=None):
+ """
+ Create a copy of this FreeTypeFont object,
+ using any specified arguments to override the settings.
+
+ Parameters are identical to the parameters used to initialize this
+ object, minus the deprecated 'file' argument.
+
+ :return: A FreeTypeFont object.
+ """
+ return FreeTypeFont(font=self.path if font is None else font,
+ size=self.size if size is None else size,
+ index=self.index if index is None else index,
+ encoding=self.encoding if encoding is None else
+ encoding)
+
+##
+# Wrapper that creates a transposed font from any existing font
+# object.
+#
+# @param font A font object.
+# @param orientation An optional orientation. If given, this should
+# be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM,
+# Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270.
+
+
+class TransposedFont:
+ "Wrapper for writing rotated or mirrored text"
+
+ def __init__(self, font, orientation=None):
+ self.font = font
+ self.orientation = orientation # any 'transpose' argument, or None
+
+ def getsize(self, text):
+ w, h = self.font.getsize(text)
+ if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
+ return h, w
+ return w, h
+
+ def getmask(self, text, mode=""):
+ im = self.font.getmask(text, mode)
+ if self.orientation is not None:
+ return im.transpose(self.orientation)
+ return im
+
+
+def load(filename):
+ """
+ Load a font file. This function loads a font object from the given
+ bitmap font file, and returns the corresponding font object.
+
+ :param filename: Name of font file.
+ :return: A font object.
+ :exception IOError: If the file could not be read.
+ """
+ f = ImageFont()
+ f._load_pilfont(filename)
+ return f
+
+
+def truetype(font=None, size=10, index=0, encoding="", filename=None):
+ """
+ Load a TrueType or OpenType font file, and create a font object.
+ This function loads a font object from the given file, and creates
+ a font object for a font of the given size.
+
+ This function requires the _imagingft service.
+
+ :param font: A truetype font file. Under Windows, if the file
+ is not found in this filename, the loader also looks in
+ Windows :file:`fonts/` directory.
+ :param size: The requested size, in points.
+ :param index: Which font face to load (default is first available face).
+ :param encoding: Which font encoding to use (default is Unicode). Common
+ encodings are "unic" (Unicode), "symb" (Microsoft
+ Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
+ and "armn" (Apple Roman). See the FreeType documentation
+ for more information.
+ :param filename: Deprecated. Please use font instead.
+ :return: A font object.
+ :exception IOError: If the file could not be read.
+ """
+
+ if filename:
+ if warnings:
+ warnings.warn(
+ 'filename parameter deprecated, '
+ 'please use font parameter instead.',
+ DeprecationWarning)
+ font = filename
+
+ try:
+ return FreeTypeFont(font, size, index, encoding)
+ except IOError:
+ ttf_filename = os.path.basename(font)
+
+ dirs = []
+ if sys.platform == "win32":
+ # check the windows font repository
+ # NOTE: must use uppercase WINDIR, to work around bugs in
+ # 1.5.2's os.environ.get()
+ windir = os.environ.get("WINDIR")
+ if windir:
+ dirs.append(os.path.join(windir, "fonts"))
+ elif sys.platform in ('linux', 'linux2'):
+ lindirs = os.environ.get("XDG_DATA_DIRS", "")
+ if not lindirs:
+ # According to the freedesktop spec, XDG_DATA_DIRS should
+ # default to /usr/share
+ lindirs = '/usr/share'
+ dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
+ elif sys.platform == 'darwin':
+ dirs += ['/Library/Fonts', '/System/Library/Fonts',
+ os.path.expanduser('~/Library/Fonts')]
+
+ ext = os.path.splitext(ttf_filename)[1]
+ first_font_with_a_different_extension = None
+ for directory in dirs:
+ for walkroot, walkdir, walkfilenames in os.walk(directory):
+ for walkfilename in walkfilenames:
+ if ext and walkfilename == ttf_filename:
+ fontpath = os.path.join(walkroot, walkfilename)
+ return FreeTypeFont(fontpath, size, index, encoding)
+ elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
+ fontpath = os.path.join(walkroot, walkfilename)
+ if os.path.splitext(fontpath)[1] == '.ttf':
+ return FreeTypeFont(fontpath, size, index, encoding)
+ if not ext and first_font_with_a_different_extension is None:
+ first_font_with_a_different_extension = fontpath
+ if first_font_with_a_different_extension:
+ return FreeTypeFont(first_font_with_a_different_extension, size,
+ index, encoding)
+ raise
+
+
+def load_path(filename):
+ """
+ Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
+ bitmap font along the Python path.
+
+ :param filename: Name of font file.
+ :return: A font object.
+ :exception IOError: If the file could not be read.
+ """
+ for directory in sys.path:
+ if isDirectory(directory):
+ if not isinstance(filename, str):
+ if bytes is str:
+ filename = filename.encode("utf-8")
+ else:
+ filename = filename.decode("utf-8")
+ try:
+ return load(os.path.join(directory, filename))
+ except IOError:
+ pass
+ raise IOError("cannot find font file")
+
+
+def load_default():
+ """Load a "better than nothing" default font.
+
+ .. versionadded:: 1.1.4
+
+ :return: A font object.
+ """
+ from io import BytesIO
+ import base64
+ f = ImageFont()
+ f._load_pilfont_data(
+ # courB08
+ BytesIO(base64.decodestring(b'''
+UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
+BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
+AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
+AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
+ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
+BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
+//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
+AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
+AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
+ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
+AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
+/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
+AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
+AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
+AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
+BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
+AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
+2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
+AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
++gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
+////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
+BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
+AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
+AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
+AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
+BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
+//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
+AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
+AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
+mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
+AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
+AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
+AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
+Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
+//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
+AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
+AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
+DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
+AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
++wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
+AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
+///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
+AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
+BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
+Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
+eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
+AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
++gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
+////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
+BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
+AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
+AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
+Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
+Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
+//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
+AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
+AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
+LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
+AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
+AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
+AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
+AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
+AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
+EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
+AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
+pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
+AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
++QAGAAIAzgAKANUAEw==
+''')), Image.open(BytesIO(base64.decodestring(b'''
+iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
+Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
+M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
+LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
+IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
+Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
+NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
+in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
+SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
+AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
+y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
+ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
+lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
+/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
+AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
+c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
+/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
+pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
+oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
+evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
+AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
+Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
+w7IkEbzhVQAAAABJRU5ErkJggg==
+'''))))
+ return f
+
+# End of file
diff --git a/lib/Python/Lib/PIL/ImageGrab.py b/lib/Python/Lib/PIL/ImageGrab.py
new file mode 100644
index 000000000..ef0135334
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageGrab.py
@@ -0,0 +1,52 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# screen grabber (windows only)
+#
+# History:
+# 2001-04-26 fl created
+# 2001-09-17 fl use builtin driver, if present
+# 2002-11-19 fl added grabclipboard support
+#
+# Copyright (c) 2001-2002 by Secret Labs AB
+# Copyright (c) 2001-2002 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+
+import sys
+if sys.platform != "win32":
+ raise ImportError("ImageGrab is Windows only")
+
+try:
+ # built-in driver (1.1.3 and later)
+ grabber = Image.core.grabscreen
+except AttributeError:
+ # stand-alone driver (pil plus)
+ import _grabscreen
+ grabber = _grabscreen.grab
+
+
+def grab(bbox=None):
+ size, data = grabber()
+ im = Image.frombytes(
+ "RGB", size, data,
+ # RGB, 32-bit line padding, origo in lower left corner
+ "raw", "BGR", (size[0]*3 + 3) & -4, -1
+ )
+ if bbox:
+ im = im.crop(bbox)
+ return im
+
+
+def grabclipboard():
+ debug = 0 # temporary interface
+ data = Image.core.grabclipboard(debug)
+ if isinstance(data, bytes):
+ from PIL import BmpImagePlugin
+ import io
+ return BmpImagePlugin.DibImageFile(io.BytesIO(data))
+ return data
diff --git a/lib/Python/Lib/PIL/ImageMath.py b/lib/Python/Lib/PIL/ImageMath.py
new file mode 100644
index 000000000..4dcc5125c
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageMath.py
@@ -0,0 +1,270 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# a simple math add-on for the Python Imaging Library
+#
+# History:
+# 1999-02-15 fl Original PIL Plus release
+# 2005-05-05 fl Simplified and cleaned up for PIL 1.1.6
+# 2005-09-12 fl Fixed int() and float() for Python 2.4.1
+#
+# Copyright (c) 1999-2005 by Secret Labs AB
+# Copyright (c) 2005 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+from PIL import _imagingmath
+
+try:
+ import builtins
+except ImportError:
+ import __builtin__
+ builtins = __builtin__
+
+VERBOSE = 0
+
+
+def _isconstant(v):
+ return isinstance(v, int) or isinstance(v, float)
+
+
+class _Operand:
+ # wraps an image operand, providing standard operators
+
+ def __init__(self, im):
+ self.im = im
+
+ def __fixup(self, im1):
+ # convert image to suitable mode
+ if isinstance(im1, _Operand):
+ # argument was an image.
+ if im1.im.mode in ("1", "L"):
+ return im1.im.convert("I")
+ elif im1.im.mode in ("I", "F"):
+ return im1.im
+ else:
+ raise ValueError("unsupported mode: %s" % im1.im.mode)
+ else:
+ # argument was a constant
+ if _isconstant(im1) and self.im.mode in ("1", "L", "I"):
+ return Image.new("I", self.im.size, im1)
+ else:
+ return Image.new("F", self.im.size, im1)
+
+ def apply(self, op, im1, im2=None, mode=None):
+ im1 = self.__fixup(im1)
+ if im2 is None:
+ # unary operation
+ out = Image.new(mode or im1.mode, im1.size, None)
+ im1.load()
+ try:
+ op = getattr(_imagingmath, op+"_"+im1.mode)
+ except AttributeError:
+ raise TypeError("bad operand type for '%s'" % op)
+ _imagingmath.unop(op, out.im.id, im1.im.id)
+ else:
+ # binary operation
+ im2 = self.__fixup(im2)
+ if im1.mode != im2.mode:
+ # convert both arguments to floating point
+ if im1.mode != "F":
+ im1 = im1.convert("F")
+ if im2.mode != "F":
+ im2 = im2.convert("F")
+ if im1.mode != im2.mode:
+ raise ValueError("mode mismatch")
+ if im1.size != im2.size:
+ # crop both arguments to a common size
+ size = (min(im1.size[0], im2.size[0]),
+ min(im1.size[1], im2.size[1]))
+ if im1.size != size:
+ im1 = im1.crop((0, 0) + size)
+ if im2.size != size:
+ im2 = im2.crop((0, 0) + size)
+ out = Image.new(mode or im1.mode, size, None)
+ else:
+ out = Image.new(mode or im1.mode, im1.size, None)
+ im1.load()
+ im2.load()
+ try:
+ op = getattr(_imagingmath, op+"_"+im1.mode)
+ except AttributeError:
+ raise TypeError("bad operand type for '%s'" % op)
+ _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id)
+ return _Operand(out)
+
+ # unary operators
+ def __bool__(self):
+ # an image is "true" if it contains at least one non-zero pixel
+ return self.im.getbbox() is not None
+
+ if bytes is str:
+ # Provide __nonzero__ for pre-Py3k
+ __nonzero__ = __bool__
+ del __bool__
+
+ def __abs__(self):
+ return self.apply("abs", self)
+
+ def __pos__(self):
+ return self
+
+ def __neg__(self):
+ return self.apply("neg", self)
+
+ # binary operators
+ def __add__(self, other):
+ return self.apply("add", self, other)
+
+ def __radd__(self, other):
+ return self.apply("add", other, self)
+
+ def __sub__(self, other):
+ return self.apply("sub", self, other)
+
+ def __rsub__(self, other):
+ return self.apply("sub", other, self)
+
+ def __mul__(self, other):
+ return self.apply("mul", self, other)
+
+ def __rmul__(self, other):
+ return self.apply("mul", other, self)
+
+ def __truediv__(self, other):
+ return self.apply("div", self, other)
+
+ def __rtruediv__(self, other):
+ return self.apply("div", other, self)
+
+ def __mod__(self, other):
+ return self.apply("mod", self, other)
+
+ def __rmod__(self, other):
+ return self.apply("mod", other, self)
+
+ def __pow__(self, other):
+ return self.apply("pow", self, other)
+
+ def __rpow__(self, other):
+ return self.apply("pow", other, self)
+
+ if bytes is str:
+ # Provide __div__ and __rdiv__ for pre-Py3k
+ __div__ = __truediv__
+ __rdiv__ = __rtruediv__
+ del __truediv__
+ del __rtruediv__
+
+ # bitwise
+ def __invert__(self):
+ return self.apply("invert", self)
+
+ def __and__(self, other):
+ return self.apply("and", self, other)
+
+ def __rand__(self, other):
+ return self.apply("and", other, self)
+
+ def __or__(self, other):
+ return self.apply("or", self, other)
+
+ def __ror__(self, other):
+ return self.apply("or", other, self)
+
+ def __xor__(self, other):
+ return self.apply("xor", self, other)
+
+ def __rxor__(self, other):
+ return self.apply("xor", other, self)
+
+ def __lshift__(self, other):
+ return self.apply("lshift", self, other)
+
+ def __rshift__(self, other):
+ return self.apply("rshift", self, other)
+
+ # logical
+ def __eq__(self, other):
+ return self.apply("eq", self, other)
+
+ def __ne__(self, other):
+ return self.apply("ne", self, other)
+
+ def __lt__(self, other):
+ return self.apply("lt", self, other)
+
+ def __le__(self, other):
+ return self.apply("le", self, other)
+
+ def __gt__(self, other):
+ return self.apply("gt", self, other)
+
+ def __ge__(self, other):
+ return self.apply("ge", self, other)
+
+
+# conversions
+def imagemath_int(self):
+ return _Operand(self.im.convert("I"))
+
+
+def imagemath_float(self):
+ return _Operand(self.im.convert("F"))
+
+
+# logical
+def imagemath_equal(self, other):
+ return self.apply("eq", self, other, mode="I")
+
+
+def imagemath_notequal(self, other):
+ return self.apply("ne", self, other, mode="I")
+
+
+def imagemath_min(self, other):
+ return self.apply("min", self, other)
+
+
+def imagemath_max(self, other):
+ return self.apply("max", self, other)
+
+
+def imagemath_convert(self, mode):
+ return _Operand(self.im.convert(mode))
+
+ops = {}
+for k, v in list(globals().items()):
+ if k[:10] == "imagemath_":
+ ops[k[10:]] = v
+
+
+def eval(expression, _dict={}, **kw):
+ """
+ Evaluates an image expression.
+
+ :param expression: A string containing a Python-style expression.
+ :param options: Values to add to the evaluation context. You
+ can either use a dictionary, or one or more keyword
+ arguments.
+ :return: The evaluated expression. This is usually an image object, but can
+ also be an integer, a floating point value, or a pixel tuple,
+ depending on the expression.
+ """
+
+ # build execution namespace
+ args = ops.copy()
+ args.update(_dict)
+ args.update(kw)
+ for k, v in list(args.items()):
+ if hasattr(v, "im"):
+ args[k] = _Operand(v)
+
+ out = builtins.eval(expression, args)
+ try:
+ return out.im
+ except AttributeError:
+ return out
diff --git a/lib/Python/Lib/PIL/ImageMode.py b/lib/Python/Lib/PIL/ImageMode.py
new file mode 100644
index 000000000..295069108
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageMode.py
@@ -0,0 +1,52 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# standard mode descriptors
+#
+# History:
+# 2006-03-20 fl Added
+#
+# Copyright (c) 2006 by Secret Labs AB.
+# Copyright (c) 2006 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# mode descriptor cache
+_modes = {}
+
+
+##
+# Wrapper for mode strings.
+
+class ModeDescriptor:
+
+ def __init__(self, mode, bands, basemode, basetype):
+ self.mode = mode
+ self.bands = bands
+ self.basemode = basemode
+ self.basetype = basetype
+
+ def __str__(self):
+ return self.mode
+
+
+##
+# Gets a mode descriptor for the given mode.
+
+def getmode(mode):
+ if not _modes:
+ # initialize mode cache
+ from PIL import Image
+ # core modes
+ for m, (basemode, basetype, bands) in Image._MODEINFO.items():
+ _modes[m] = ModeDescriptor(m, bands, basemode, basetype)
+ # extra experimental modes
+ _modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
+ _modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
+ # mapping modes
+ _modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")
+ _modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L")
+ _modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L")
+ return _modes[mode]
diff --git a/lib/Python/Lib/PIL/ImageMorph.py b/lib/Python/Lib/PIL/ImageMorph.py
new file mode 100644
index 000000000..996eacb7d
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageMorph.py
@@ -0,0 +1,245 @@
+# A binary morphology add-on for the Python Imaging Library
+#
+# History:
+# 2014-06-04 Initial version.
+#
+# Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com>
+
+from PIL import Image
+from PIL import _imagingmorph
+import re
+
+LUT_SIZE = 1 << 9
+
+
+class LutBuilder:
+ """A class for building a MorphLut from a descriptive language
+
+ The input patterns is a list of a strings sequences like these::
+
+ 4:(...
+ .1.
+ 111)->1
+
+ (whitespaces including linebreaks are ignored). The option 4
+ describes a series of symmetry operations (in this case a
+ 4-rotation), the pattern is described by:
+
+ - . or X - Ignore
+ - 1 - Pixel is on
+ - 0 - Pixel is off
+
+ The result of the operation is described after "->" string.
+
+ The default is to return the current pixel value, which is
+ returned if no other match is found.
+
+ Operations:
+
+ - 4 - 4 way rotation
+ - N - Negate
+ - 1 - Dummy op for no other operation (an op must always be given)
+ - M - Mirroring
+
+ Example::
+
+ lb = LutBuilder(patterns = ["4:(... .1. 111)->1"])
+ lut = lb.build_lut()
+
+ """
+ def __init__(self, patterns=None, op_name=None):
+ if patterns is not None:
+ self.patterns = patterns
+ else:
+ self.patterns = []
+ self.lut = None
+ if op_name is not None:
+ known_patterns = {
+ 'corner': ['1:(... ... ...)->0',
+ '4:(00. 01. ...)->1'],
+ 'dilation4': ['4:(... .0. .1.)->1'],
+ 'dilation8': ['4:(... .0. .1.)->1',
+ '4:(... .0. ..1)->1'],
+ 'erosion4': ['4:(... .1. .0.)->0'],
+ 'erosion8': ['4:(... .1. .0.)->0',
+ '4:(... .1. ..0)->0'],
+ 'edge': ['1:(... ... ...)->0',
+ '4:(.0. .1. ...)->1',
+ '4:(01. .1. ...)->1']
+ }
+ if op_name not in known_patterns:
+ raise Exception('Unknown pattern '+op_name+'!')
+
+ self.patterns = known_patterns[op_name]
+
+ def add_patterns(self, patterns):
+ self.patterns += patterns
+
+ def build_default_lut(self):
+ symbols = [0, 1]
+ m = 1 << 4 # pos of current pixel
+ self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)])
+
+ def get_lut(self):
+ return self.lut
+
+ def _string_permute(self, pattern, permutation):
+ """string_permute takes a pattern and a permutation and returns the
+ string permuted according to the permutation list.
+ """
+ assert(len(permutation) == 9)
+ return ''.join([pattern[p] for p in permutation])
+
+ def _pattern_permute(self, basic_pattern, options, basic_result):
+ """pattern_permute takes a basic pattern and its result and clones
+ the pattern according to the modifications described in the $options
+ parameter. It returns a list of all cloned patterns."""
+ patterns = [(basic_pattern, basic_result)]
+
+ # rotations
+ if '4' in options:
+ res = patterns[-1][1]
+ for i in range(4):
+ patterns.append(
+ (self._string_permute(patterns[-1][0], [6, 3, 0,
+ 7, 4, 1,
+ 8, 5, 2]), res))
+ # mirror
+ if 'M' in options:
+ n = len(patterns)
+ for pattern, res in patterns[0:n]:
+ patterns.append(
+ (self._string_permute(pattern, [2, 1, 0,
+ 5, 4, 3,
+ 8, 7, 6]), res))
+
+ # negate
+ if 'N' in options:
+ n = len(patterns)
+ for pattern, res in patterns[0:n]:
+ # Swap 0 and 1
+ pattern = (pattern
+ .replace('0', 'Z')
+ .replace('1', '0')
+ .replace('Z', '1'))
+ res = '%d' % (1-int(res))
+ patterns.append((pattern, res))
+
+ return patterns
+
+ def build_lut(self):
+ """Compile all patterns into a morphology lut.
+
+ TBD :Build based on (file) morphlut:modify_lut
+ """
+ self.build_default_lut()
+ patterns = []
+
+ # Parse and create symmetries of the patterns strings
+ for p in self.patterns:
+ m = re.search(
+ r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', ''))
+ if not m:
+ raise Exception('Syntax error in pattern "'+p+'"')
+ options = m.group(1)
+ pattern = m.group(2)
+ result = int(m.group(3))
+
+ # Get rid of spaces
+ pattern = pattern.replace(' ', '').replace('\n', '')
+
+ patterns += self._pattern_permute(pattern, options, result)
+
+# # Debugging
+# for p,r in patterns:
+# print p,r
+# print '--'
+
+ # compile the patterns into regular expressions for speed
+ for i in range(len(patterns)):
+ p = patterns[i][0].replace('.', 'X').replace('X', '[01]')
+ p = re.compile(p)
+ patterns[i] = (p, patterns[i][1])
+
+ # Step through table and find patterns that match.
+ # Note that all the patterns are searched. The last one
+ # caught overrides
+ for i in range(LUT_SIZE):
+ # Build the bit pattern
+ bitpattern = bin(i)[2:]
+ bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1]
+
+ for p, r in patterns:
+ if p.match(bitpattern):
+ self.lut[i] = [0, 1][r]
+
+ return self.lut
+
+
+class MorphOp:
+ """A class for binary morphological operators"""
+
+ def __init__(self,
+ lut=None,
+ op_name=None,
+ patterns=None):
+ """Create a binary morphological operator"""
+ self.lut = lut
+ if op_name is not None:
+ self.lut = LutBuilder(op_name=op_name).build_lut()
+ elif patterns is not None:
+ self.lut = LutBuilder(patterns=patterns).build_lut()
+
+ def apply(self, image):
+ """Run a single morphological operation on an image
+
+ Returns a tuple of the number of changed pixels and the
+ morphed image"""
+ if self.lut is None:
+ raise Exception('No operator loaded')
+
+ outimage = Image.new(image.mode, image.size, None)
+ count = _imagingmorph.apply(
+ bytes(self.lut), image.im.id, outimage.im.id)
+ return count, outimage
+
+ def match(self, image):
+ """Get a list of coordinates matching the morphological operation on
+ an image.
+
+ Returns a list of tuples of (x,y) coordinates
+ of all matching pixels."""
+ if self.lut is None:
+ raise Exception('No operator loaded')
+
+ return _imagingmorph.match(bytes(self.lut), image.im.id)
+
+ def get_on_pixels(self, image):
+ """Get a list of all turned on pixels in a binary image
+
+ Returns a list of tuples of (x,y) coordinates
+ of all matching pixels."""
+
+ return _imagingmorph.get_on_pixels(image.im.id)
+
+ def load_lut(self, filename):
+ """Load an operator from an mrl file"""
+ with open(filename, 'rb') as f:
+ self.lut = bytearray(f.read())
+
+ if len(self.lut) != 8192:
+ self.lut = None
+ raise Exception('Wrong size operator file!')
+
+ def save_lut(self, filename):
+ """Save an operator to an mrl file"""
+ if self.lut is None:
+ raise Exception('No operator loaded')
+ with open(filename, 'wb') as f:
+ f.write(self.lut)
+
+ def set_lut(self, lut):
+ """Set the lut from an external source"""
+ self.lut = lut
+
+# End of file
diff --git a/lib/Python/Lib/PIL/ImageOps.py b/lib/Python/Lib/PIL/ImageOps.py
new file mode 100644
index 000000000..a1706875d
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageOps.py
@@ -0,0 +1,462 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# standard image operations
+#
+# History:
+# 2001-10-20 fl Created
+# 2001-10-23 fl Added autocontrast operator
+# 2001-12-18 fl Added Kevin's fit operator
+# 2004-03-14 fl Fixed potential division by zero in equalize
+# 2005-05-05 fl Fixed equalize for low number of values
+#
+# Copyright (c) 2001-2004 by Secret Labs AB
+# Copyright (c) 2001-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+from PIL._util import isStringType
+import operator
+from functools import reduce
+
+
+#
+# helpers
+
+def _border(border):
+ if isinstance(border, tuple):
+ if len(border) == 2:
+ left, top = right, bottom = border
+ elif len(border) == 4:
+ left, top, right, bottom = border
+ else:
+ left = top = right = bottom = border
+ return left, top, right, bottom
+
+
+def _color(color, mode):
+ if isStringType(color):
+ from PIL import ImageColor
+ color = ImageColor.getcolor(color, mode)
+ return color
+
+
+def _lut(image, lut):
+ if image.mode == "P":
+ # FIXME: apply to lookup table, not image data
+ raise NotImplementedError("mode P support coming soon")
+ elif image.mode in ("L", "RGB"):
+ if image.mode == "RGB" and len(lut) == 256:
+ lut = lut + lut + lut
+ return image.point(lut)
+ else:
+ raise IOError("not supported for this image mode")
+
+#
+# actions
+
+
+def autocontrast(image, cutoff=0, ignore=None):
+ """
+ Maximize (normalize) image contrast. This function calculates a
+ histogram of the input image, removes **cutoff** percent of the
+ lightest and darkest pixels from the histogram, and remaps the image
+ so that the darkest pixel becomes black (0), and the lightest
+ becomes white (255).
+
+ :param image: The image to process.
+ :param cutoff: How many percent to cut off from the histogram.
+ :param ignore: The background pixel value (use None for no background).
+ :return: An image.
+ """
+ histogram = image.histogram()
+ lut = []
+ for layer in range(0, len(histogram), 256):
+ h = histogram[layer:layer+256]
+ if ignore is not None:
+ # get rid of outliers
+ try:
+ h[ignore] = 0
+ except TypeError:
+ # assume sequence
+ for ix in ignore:
+ h[ix] = 0
+ if cutoff:
+ # cut off pixels from both ends of the histogram
+ # get number of pixels
+ n = 0
+ for ix in range(256):
+ n = n + h[ix]
+ # remove cutoff% pixels from the low end
+ cut = n * cutoff // 100
+ for lo in range(256):
+ if cut > h[lo]:
+ cut = cut - h[lo]
+ h[lo] = 0
+ else:
+ h[lo] -= cut
+ cut = 0
+ if cut <= 0:
+ break
+ # remove cutoff% samples from the hi end
+ cut = n * cutoff // 100
+ for hi in range(255, -1, -1):
+ if cut > h[hi]:
+ cut = cut - h[hi]
+ h[hi] = 0
+ else:
+ h[hi] -= cut
+ cut = 0
+ if cut <= 0:
+ break
+ # find lowest/highest samples after preprocessing
+ for lo in range(256):
+ if h[lo]:
+ break
+ for hi in range(255, -1, -1):
+ if h[hi]:
+ break
+ if hi <= lo:
+ # don't bother
+ lut.extend(list(range(256)))
+ else:
+ scale = 255.0 / (hi - lo)
+ offset = -lo * scale
+ for ix in range(256):
+ ix = int(ix * scale + offset)
+ if ix < 0:
+ ix = 0
+ elif ix > 255:
+ ix = 255
+ lut.append(ix)
+ return _lut(image, lut)
+
+
+def colorize(image, black, white):
+ """
+ Colorize grayscale image. The **black** and **white**
+ arguments should be RGB tuples; this function calculates a color
+ wedge mapping all black pixels in the source image to the first
+ color, and all white pixels to the second color.
+
+ :param image: The image to colorize.
+ :param black: The color to use for black input pixels.
+ :param white: The color to use for white input pixels.
+ :return: An image.
+ """
+ assert image.mode == "L"
+ black = _color(black, "RGB")
+ white = _color(white, "RGB")
+ red = []
+ green = []
+ blue = []
+ for i in range(256):
+ red.append(black[0]+i*(white[0]-black[0])//255)
+ green.append(black[1]+i*(white[1]-black[1])//255)
+ blue.append(black[2]+i*(white[2]-black[2])//255)
+ image = image.convert("RGB")
+ return _lut(image, red + green + blue)
+
+
+def crop(image, border=0):
+ """
+ Remove border from image. The same amount of pixels are removed
+ from all four sides. This function works on all image modes.
+
+ .. seealso:: :py:meth:`~PIL.Image.Image.crop`
+
+ :param image: The image to crop.
+ :param border: The number of pixels to remove.
+ :return: An image.
+ """
+ left, top, right, bottom = _border(border)
+ return image.crop(
+ (left, top, image.size[0]-right, image.size[1]-bottom)
+ )
+
+
+def deform(image, deformer, resample=Image.BILINEAR):
+ """
+ Deform the image.
+
+ :param image: The image to deform.
+ :param deformer: A deformer object. Any object that implements a
+ **getmesh** method can be used.
+ :param resample: What resampling filter to use.
+ :return: An image.
+ """
+ return image.transform(
+ image.size, Image.MESH, deformer.getmesh(image), resample
+ )
+
+
+def equalize(image, mask=None):
+ """
+ Equalize the image histogram. This function applies a non-linear
+ mapping to the input image, in order to create a uniform
+ distribution of grayscale values in the output image.
+
+ :param image: The image to equalize.
+ :param mask: An optional mask. If given, only the pixels selected by
+ the mask are included in the analysis.
+ :return: An image.
+ """
+ if image.mode == "P":
+ image = image.convert("RGB")
+ h = image.histogram(mask)
+ lut = []
+ for b in range(0, len(h), 256):
+ histo = [_f for _f in h[b:b+256] if _f]
+ if len(histo) <= 1:
+ lut.extend(list(range(256)))
+ else:
+ step = (reduce(operator.add, histo) - histo[-1]) // 255
+ if not step:
+ lut.extend(list(range(256)))
+ else:
+ n = step // 2
+ for i in range(256):
+ lut.append(n // step)
+ n = n + h[i+b]
+ return _lut(image, lut)
+
+
+def expand(image, border=0, fill=0):
+ """
+ Add border to the image
+
+ :param image: The image to expand.
+ :param border: Border width, in pixels.
+ :param fill: Pixel fill value (a color value). Default is 0 (black).
+ :return: An image.
+ """
+ "Add border to image"
+ left, top, right, bottom = _border(border)
+ width = left + image.size[0] + right
+ height = top + image.size[1] + bottom
+ out = Image.new(image.mode, (width, height), _color(fill, image.mode))
+ out.paste(image, (left, top))
+ return out
+
+
+def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
+ """
+ Returns a sized and cropped version of the image, cropped to the
+ requested aspect ratio and size.
+
+ This function was contributed by Kevin Cazabon.
+
+ :param size: The requested output size in pixels, given as a
+ (width, height) tuple.
+ :param method: What resampling method to use. Default is
+ :py:attr:`PIL.Image.NEAREST`.
+ :param bleed: Remove a border around the outside of the image (from all
+ four edges. The value is a decimal percentage (use 0.01 for
+ one percent). The default value is 0 (no border).
+ :param centering: Control the cropping position. Use (0.5, 0.5) for
+ center cropping (e.g. if cropping the width, take 50% off
+ of the left side, and therefore 50% off the right side).
+ (0.0, 0.0) will crop from the top left corner (i.e. if
+ cropping the width, take all of the crop off of the right
+ side, and if cropping the height, take all of it off the
+ bottom). (1.0, 0.0) will crop from the bottom left
+ corner, etc. (i.e. if cropping the width, take all of the
+ crop off the left side, and if cropping the height take
+ none from the top, and therefore all off the bottom).
+ :return: An image.
+ """
+
+ # by Kevin Cazabon, Feb 17/2000
+ # kevin@cazabon.com
+ # http://www.cazabon.com
+
+ # ensure inputs are valid
+ if not isinstance(centering, list):
+ centering = [centering[0], centering[1]]
+
+ if centering[0] > 1.0 or centering[0] < 0.0:
+ centering[0] = 0.50
+ if centering[1] > 1.0 or centering[1] < 0.0:
+ centering[1] = 0.50
+
+ if bleed > 0.49999 or bleed < 0.0:
+ bleed = 0.0
+
+ # calculate the area to use for resizing and cropping, subtracting
+ # the 'bleed' around the edges
+
+ # number of pixels to trim off on Top and Bottom, Left and Right
+ bleedPixels = (
+ int((float(bleed) * float(image.size[0])) + 0.5),
+ int((float(bleed) * float(image.size[1])) + 0.5)
+ )
+
+ liveArea = (0, 0, image.size[0], image.size[1])
+ if bleed > 0.0:
+ liveArea = (
+ bleedPixels[0], bleedPixels[1], image.size[0] - bleedPixels[0] - 1,
+ image.size[1] - bleedPixels[1] - 1
+ )
+
+ liveSize = (liveArea[2] - liveArea[0], liveArea[3] - liveArea[1])
+
+ # calculate the aspect ratio of the liveArea
+ liveAreaAspectRatio = float(liveSize[0])/float(liveSize[1])
+
+ # calculate the aspect ratio of the output image
+ aspectRatio = float(size[0]) / float(size[1])
+
+ # figure out if the sides or top/bottom will be cropped off
+ if liveAreaAspectRatio >= aspectRatio:
+ # liveArea is wider than what's needed, crop the sides
+ cropWidth = int((aspectRatio * float(liveSize[1])) + 0.5)
+ cropHeight = liveSize[1]
+ else:
+ # liveArea is taller than what's needed, crop the top and bottom
+ cropWidth = liveSize[0]
+ cropHeight = int((float(liveSize[0])/aspectRatio) + 0.5)
+
+ # make the crop
+ leftSide = int(liveArea[0] + (float(liveSize[0]-cropWidth) * centering[0]))
+ if leftSide < 0:
+ leftSide = 0
+ topSide = int(liveArea[1] + (float(liveSize[1]-cropHeight) * centering[1]))
+ if topSide < 0:
+ topSide = 0
+
+ out = image.crop(
+ (leftSide, topSide, leftSide + cropWidth, topSide + cropHeight)
+ )
+
+ # resize the image and return it
+ return out.resize(size, method)
+
+
+def flip(image):
+ """
+ Flip the image vertically (top to bottom).
+
+ :param image: The image to flip.
+ :return: An image.
+ """
+ return image.transpose(Image.FLIP_TOP_BOTTOM)
+
+
+def grayscale(image):
+ """
+ Convert the image to grayscale.
+
+ :param image: The image to convert.
+ :return: An image.
+ """
+ return image.convert("L")
+
+
+def invert(image):
+ """
+ Invert (negate) the image.
+
+ :param image: The image to invert.
+ :return: An image.
+ """
+ lut = []
+ for i in range(256):
+ lut.append(255-i)
+ return _lut(image, lut)
+
+
+def mirror(image):
+ """
+ Flip image horizontally (left to right).
+
+ :param image: The image to mirror.
+ :return: An image.
+ """
+ return image.transpose(Image.FLIP_LEFT_RIGHT)
+
+
+def posterize(image, bits):
+ """
+ Reduce the number of bits for each color channel.
+
+ :param image: The image to posterize.
+ :param bits: The number of bits to keep for each channel (1-8).
+ :return: An image.
+ """
+ lut = []
+ mask = ~(2**(8-bits)-1)
+ for i in range(256):
+ lut.append(i & mask)
+ return _lut(image, lut)
+
+
+def solarize(image, threshold=128):
+ """
+ Invert all pixel values above a threshold.
+
+ :param image: The image to solarize.
+ :param threshold: All pixels above this greyscale level are inverted.
+ :return: An image.
+ """
+ lut = []
+ for i in range(256):
+ if i < threshold:
+ lut.append(i)
+ else:
+ lut.append(255-i)
+ return _lut(image, lut)
+
+
+# --------------------------------------------------------------------
+# PIL USM components, from Kevin Cazabon.
+
+def gaussian_blur(im, radius=None):
+ """ PIL_usm.gblur(im, [radius])"""
+
+ if radius is None:
+ radius = 5.0
+
+ im.load()
+
+ return im.im.gaussian_blur(radius)
+
+gblur = gaussian_blur
+
+
+def unsharp_mask(im, radius=None, percent=None, threshold=None):
+ """ PIL_usm.usm(im, [radius, percent, threshold])"""
+
+ if radius is None:
+ radius = 5.0
+ if percent is None:
+ percent = 150
+ if threshold is None:
+ threshold = 3
+
+ im.load()
+
+ return im.im.unsharp_mask(radius, percent, threshold)
+
+usm = unsharp_mask
+
+
+def box_blur(image, radius):
+ """
+ Blur the image by setting each pixel to the average value of the pixels
+ in a square box extending radius pixels in each direction.
+ Supports float radius of arbitrary size. Uses an optimized implementation
+ which runs in linear time relative to the size of the image
+ for any radius value.
+
+ :param image: The image to blur.
+ :param radius: Size of the box in one direction. Radius 0 does not blur,
+ returns an identical image. Radius 1 takes 1 pixel
+ in each direction, i.e. 9 pixels in total.
+ :return: An image.
+ """
+ image.load()
+
+ return image._new(image.im.box_blur(radius))
diff --git a/lib/Python/Lib/PIL/ImagePalette.py b/lib/Python/Lib/PIL/ImagePalette.py
new file mode 100644
index 000000000..62f8814f1
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImagePalette.py
@@ -0,0 +1,235 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# image palette object
+#
+# History:
+# 1996-03-11 fl Rewritten.
+# 1997-01-03 fl Up and running.
+# 1997-08-23 fl Added load hack
+# 2001-04-16 fl Fixed randint shadow bug in random()
+#
+# Copyright (c) 1997-2001 by Secret Labs AB
+# Copyright (c) 1996-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import array
+import warnings
+from PIL import ImageColor
+
+
+class ImagePalette:
+ "Color palette for palette mapped images"
+
+ def __init__(self, mode="RGB", palette=None, size=0):
+ self.mode = mode
+ self.rawmode = None # if set, palette contains raw data
+ self.palette = palette or list(range(256))*len(self.mode)
+ self.colors = {}
+ self.dirty = None
+ if ((size == 0 and len(self.mode)*256 != len(self.palette)) or
+ (size != 0 and size != len(self.palette))):
+ raise ValueError("wrong palette size")
+
+ def getdata(self):
+ """
+ Get palette contents in format suitable # for the low-level
+ ``im.putpalette`` primitive.
+
+ .. warning:: This method is experimental.
+ """
+ if self.rawmode:
+ return self.rawmode, self.palette
+ return self.mode + ";L", self.tobytes()
+
+ def tobytes(self):
+ """Convert palette to bytes.
+
+ .. warning:: This method is experimental.
+ """
+ if self.rawmode:
+ raise ValueError("palette contains raw palette data")
+ if isinstance(self.palette, bytes):
+ return self.palette
+ arr = array.array("B", self.palette)
+ if hasattr(arr, 'tobytes'):
+ # py3k has a tobytes, tostring is deprecated.
+ return arr.tobytes()
+ return arr.tostring()
+
+ # Declare tostring as an alias for tobytes
+ tostring = tobytes
+
+ def getcolor(self, color):
+ """Given an rgb tuple, allocate palette entry.
+
+ .. warning:: This method is experimental.
+ """
+ if self.rawmode:
+ raise ValueError("palette contains raw palette data")
+ if isinstance(color, tuple):
+ try:
+ return self.colors[color]
+ except KeyError:
+ # allocate new color slot
+ if isinstance(self.palette, bytes):
+ self.palette = [int(x) for x in self.palette]
+ index = len(self.colors)
+ if index >= 256:
+ raise ValueError("cannot allocate more than 256 colors")
+ self.colors[color] = index
+ self.palette[index] = color[0]
+ self.palette[index+256] = color[1]
+ self.palette[index+512] = color[2]
+ self.dirty = 1
+ return index
+ else:
+ raise ValueError("unknown color specifier: %r" % color)
+
+ def save(self, fp):
+ """Save palette to text file.
+
+ .. warning:: This method is experimental.
+ """
+ if self.rawmode:
+ raise ValueError("palette contains raw palette data")
+ if isinstance(fp, str):
+ fp = open(fp, "w")
+ fp.write("# Palette\n")
+ fp.write("# Mode: %s\n" % self.mode)
+ for i in range(256):
+ fp.write("%d" % i)
+ for j in range(i*len(self.mode), (i+1)*len(self.mode)):
+ try:
+ fp.write(" %d" % self.palette[j])
+ except IndexError:
+ fp.write(" 0")
+ fp.write("\n")
+ fp.close()
+
+
+# --------------------------------------------------------------------
+# Internal
+
+def raw(rawmode, data):
+ palette = ImagePalette()
+ palette.rawmode = rawmode
+ palette.palette = data
+ palette.dirty = 1
+ return palette
+
+
+# --------------------------------------------------------------------
+# Factories
+
+def _make_linear_lut(black, white):
+ warnings.warn(
+ '_make_linear_lut() is deprecated. '
+ 'Please call make_linear_lut() instead.',
+ DeprecationWarning,
+ stacklevel=2
+ )
+ return make_linear_lut(black, white)
+
+
+def _make_gamma_lut(exp):
+ warnings.warn(
+ '_make_gamma_lut() is deprecated. '
+ 'Please call make_gamma_lut() instead.',
+ DeprecationWarning,
+ stacklevel=2
+ )
+ return make_gamma_lut(exp)
+
+
+def make_linear_lut(black, white):
+ lut = []
+ if black == 0:
+ for i in range(256):
+ lut.append(white*i//255)
+ else:
+ raise NotImplementedError # FIXME
+ return lut
+
+
+def make_gamma_lut(exp):
+ lut = []
+ for i in range(256):
+ lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
+ return lut
+
+
+def negative(mode="RGB"):
+ palette = list(range(256))
+ palette.reverse()
+ return ImagePalette(mode, palette * len(mode))
+
+
+def random(mode="RGB"):
+ from random import randint
+ palette = []
+ for i in range(256*len(mode)):
+ palette.append(randint(0, 255))
+ return ImagePalette(mode, palette)
+
+
+def sepia(white="#fff0c0"):
+ r, g, b = ImageColor.getrgb(white)
+ r = make_linear_lut(0, r)
+ g = make_linear_lut(0, g)
+ b = make_linear_lut(0, b)
+ return ImagePalette("RGB", r + g + b)
+
+
+def wedge(mode="RGB"):
+ return ImagePalette(mode, list(range(256)) * len(mode))
+
+
+def load(filename):
+
+ # FIXME: supports GIMP gradients only
+
+ fp = open(filename, "rb")
+
+ lut = None
+
+ if not lut:
+ try:
+ from PIL import GimpPaletteFile
+ fp.seek(0)
+ p = GimpPaletteFile.GimpPaletteFile(fp)
+ lut = p.getpalette()
+ except (SyntaxError, ValueError):
+ # import traceback
+ # traceback.print_exc()
+ pass
+
+ if not lut:
+ try:
+ from PIL import GimpGradientFile
+ fp.seek(0)
+ p = GimpGradientFile.GimpGradientFile(fp)
+ lut = p.getpalette()
+ except (SyntaxError, ValueError):
+ # import traceback
+ # traceback.print_exc()
+ pass
+
+ if not lut:
+ try:
+ from PIL import PaletteFile
+ fp.seek(0)
+ p = PaletteFile.PaletteFile(fp)
+ lut = p.getpalette()
+ except (SyntaxError, ValueError):
+ import traceback
+ traceback.print_exc()
+ pass
+
+ if not lut:
+ raise IOError("cannot load palette")
+
+ return lut # data, rawmode
diff --git a/lib/Python/Lib/PIL/ImagePath.py b/lib/Python/Lib/PIL/ImagePath.py
new file mode 100644
index 000000000..656d5ce61
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImagePath.py
@@ -0,0 +1,66 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# path interface
+#
+# History:
+# 1996-11-04 fl Created
+# 2002-04-14 fl Added documentation stub class
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+
+
+# the Python class below is overridden by the C implementation.
+
+
+class Path:
+
+ def __init__(self, xy):
+ pass
+
+ ##
+ # Compacts the path, by removing points that are close to each
+ # other. This method modifies the path in place.
+
+ def compact(self, distance=2):
+ pass
+
+ ##
+ # Gets the bounding box.
+
+ def getbbox(self):
+ pass
+
+ ##
+ # Maps the path through a function.
+
+ def map(self, function):
+ pass
+
+ ##
+ # Converts the path to Python list.
+ #
+ # @param flat By default, this function returns a list of 2-tuples
+ # [(x, y), ...]. If this argument is true, it returns a flat
+ # list [x, y, ...] instead.
+ # @return A list of coordinates.
+
+ def tolist(self, flat=0):
+ pass
+
+ ##
+ # Transforms the path.
+
+ def transform(self, matrix):
+ pass
+
+
+# override with C implementation
+Path = Image.core.path
diff --git a/lib/Python/Lib/PIL/ImageQt.py b/lib/Python/Lib/PIL/ImageQt.py
new file mode 100644
index 000000000..22ee2ea8f
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageQt.py
@@ -0,0 +1,98 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# a simple Qt image interface.
+#
+# history:
+# 2006-06-03 fl: created
+# 2006-06-04 fl: inherit from QImage instead of wrapping it
+# 2006-06-05 fl: removed toimage helper; move string support to ImageQt
+# 2013-11-13 fl: add support for Qt5 (aurelien.ballier@cyclonit.com)
+#
+# Copyright (c) 2006 by Secret Labs AB
+# Copyright (c) 2006 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+from PIL._util import isPath
+import sys
+
+if 'PyQt4.QtGui' not in sys.modules:
+ try:
+ from PyQt5.QtGui import QImage, qRgba
+ except:
+ try:
+ from PyQt4.QtGui import QImage, qRgba
+ except:
+ from PySide.QtGui import QImage, qRgba
+
+else: #PyQt4 is used
+ from PyQt4.QtGui import QImage, qRgba
+
+##
+# (Internal) Turns an RGB color into a Qt compatible color integer.
+
+def rgb(r, g, b, a=255):
+ # use qRgb to pack the colors, and then turn the resulting long
+ # into a negative integer with the same bitpattern.
+ return (qRgba(r, g, b, a) & 0xffffffff)
+
+
+##
+# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage
+# class.
+#
+# @param im A PIL Image object, or a file name (given either as Python
+# string or a PyQt string object).
+
+class ImageQt(QImage):
+
+ def __init__(self, im):
+
+ data = None
+ colortable = None
+
+ # handle filename, if given instead of image name
+ if hasattr(im, "toUtf8"):
+ # FIXME - is this really the best way to do this?
+ im = unicode(im.toUtf8(), "utf-8")
+ if isPath(im):
+ im = Image.open(im)
+
+ if im.mode == "1":
+ format = QImage.Format_Mono
+ elif im.mode == "L":
+ format = QImage.Format_Indexed8
+ colortable = []
+ for i in range(256):
+ colortable.append(rgb(i, i, i))
+ elif im.mode == "P":
+ format = QImage.Format_Indexed8
+ colortable = []
+ palette = im.getpalette()
+ for i in range(0, len(palette), 3):
+ colortable.append(rgb(*palette[i:i+3]))
+ elif im.mode == "RGB":
+ data = im.tobytes("raw", "BGRX")
+ format = QImage.Format_RGB32
+ elif im.mode == "RGBA":
+ try:
+ data = im.tobytes("raw", "BGRA")
+ except SystemError:
+ # workaround for earlier versions
+ r, g, b, a = im.split()
+ im = Image.merge("RGBA", (b, g, r, a))
+ format = QImage.Format_ARGB32
+ else:
+ raise ValueError("unsupported image mode %r" % im.mode)
+
+ # must keep a reference, or Qt will crash!
+ self.__data = data or im.tobytes()
+
+ QImage.__init__(self, self.__data, im.size[0], im.size[1], format)
+
+ if colortable:
+ self.setColorTable(colortable)
diff --git a/lib/Python/Lib/PIL/ImageSequence.py b/lib/Python/Lib/PIL/ImageSequence.py
new file mode 100644
index 000000000..dd01e2902
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageSequence.py
@@ -0,0 +1,42 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# sequence support classes
+#
+# history:
+# 1997-02-20 fl Created
+#
+# Copyright (c) 1997 by Secret Labs AB.
+# Copyright (c) 1997 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+
+
+class Iterator:
+ """
+ This class implements an iterator object that can be used to loop
+ over an image sequence.
+
+ You can use the ``[]`` operator to access elements by index. This operator
+ will raise an :py:exc:`IndexError` if you try to access a nonexistent
+ frame.
+
+ :param im: An image object.
+ """
+
+ def __init__(self, im):
+ if not hasattr(im, "seek"):
+ raise AttributeError("im must have seek method")
+ self.im = im
+
+ def __getitem__(self, ix):
+ try:
+ if ix:
+ self.im.seek(ix)
+ return self.im
+ except EOFError:
+ raise IndexError # end of sequence
diff --git a/lib/Python/Lib/PIL/ImageShow.py b/lib/Python/Lib/PIL/ImageShow.py
new file mode 100644
index 000000000..9527dbf97
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageShow.py
@@ -0,0 +1,179 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# im.show() drivers
+#
+# History:
+# 2008-04-06 fl Created
+#
+# Copyright (c) Secret Labs AB 2008.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from __future__ import print_function
+
+from PIL import Image
+import os
+import sys
+
+if sys.version_info >= (3, 3):
+ from shlex import quote
+else:
+ from pipes import quote
+
+_viewers = []
+
+
+def register(viewer, order=1):
+ try:
+ if issubclass(viewer, Viewer):
+ viewer = viewer()
+ except TypeError:
+ pass # raised if viewer wasn't a class
+ if order > 0:
+ _viewers.append(viewer)
+ elif order < 0:
+ _viewers.insert(0, viewer)
+
+
+##
+# Displays a given image.
+#
+# @param image An image object.
+# @param title Optional title. Not all viewers can display the title.
+# @param **options Additional viewer options.
+# @return True if a suitable viewer was found, false otherwise.
+
+def show(image, title=None, **options):
+ for viewer in _viewers:
+ if viewer.show(image, title=title, **options):
+ return 1
+ return 0
+
+
+##
+# Base class for viewers.
+
+class Viewer:
+
+ # main api
+
+ def show(self, image, **options):
+
+ # save temporary image to disk
+ if image.mode[:4] == "I;16":
+ # @PIL88 @PIL101
+ # "I;16" isn't an 'official' mode, but we still want to
+ # provide a simple way to show 16-bit images.
+ base = "L"
+ # FIXME: auto-contrast if max() > 255?
+ else:
+ base = Image.getmodebase(image.mode)
+ if base != image.mode and image.mode != "1":
+ image = image.convert(base)
+
+ return self.show_image(image, **options)
+
+ # hook methods
+
+ format = None
+
+ def get_format(self, image):
+ # return format name, or None to save as PGM/PPM
+ return self.format
+
+ def get_command(self, file, **options):
+ raise NotImplementedError
+
+ def save_image(self, image):
+ # save to temporary file, and return filename
+ return image._dump(format=self.get_format(image))
+
+ def show_image(self, image, **options):
+ # display given image
+ return self.show_file(self.save_image(image), **options)
+
+ def show_file(self, file, **options):
+ # display given file
+ os.system(self.get_command(file, **options))
+ return 1
+
+# --------------------------------------------------------------------
+
+if sys.platform == "win32":
+
+ class WindowsViewer(Viewer):
+ format = "BMP"
+
+ def get_command(self, file, **options):
+ return ('start "Pillow" /WAIT "%s" '
+ '&& ping -n 2 127.0.0.1 >NUL '
+ '&& del /f "%s"' % (file, file))
+
+ register(WindowsViewer)
+
+elif sys.platform == "darwin":
+
+ class MacViewer(Viewer):
+ format = "BMP"
+
+ def get_command(self, file, **options):
+ # on darwin open returns immediately resulting in the temp
+ # file removal while app is opening
+ command = "open -a /Applications/Preview.app"
+ command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file),
+ quote(file))
+ return command
+
+ register(MacViewer)
+
+else:
+
+ # unixoids
+
+ def which(executable):
+ path = os.environ.get("PATH")
+ if not path:
+ return None
+ for dirname in path.split(os.pathsep):
+ filename = os.path.join(dirname, executable)
+ if os.path.isfile(filename):
+ # FIXME: make sure it's executable
+ return filename
+ return None
+
+ class UnixViewer(Viewer):
+ def show_file(self, file, **options):
+ command, executable = self.get_command_ex(file, **options)
+ command = "(%s %s; rm -f %s)&" % (command, quote(file),
+ quote(file))
+ os.system(command)
+ return 1
+
+ # implementations
+
+ class DisplayViewer(UnixViewer):
+ def get_command_ex(self, file, **options):
+ command = executable = "display"
+ return command, executable
+
+ if which("display"):
+ register(DisplayViewer)
+
+ class XVViewer(UnixViewer):
+ def get_command_ex(self, file, title=None, **options):
+ # note: xv is pretty outdated. most modern systems have
+ # imagemagick's display command instead.
+ command = executable = "xv"
+ if title:
+ command += " -name %s" % quote(title)
+ return command, executable
+
+ if which("xv"):
+ register(XVViewer)
+
+if __name__ == "__main__":
+ # usage: python ImageShow.py imagefile [title]
+ print(show(Image.open(sys.argv[1]), *sys.argv[2:]))
diff --git a/lib/Python/Lib/PIL/ImageStat.py b/lib/Python/Lib/PIL/ImageStat.py
new file mode 100644
index 000000000..7e023c673
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageStat.py
@@ -0,0 +1,147 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# global image statistics
+#
+# History:
+# 1996-04-05 fl Created
+# 1997-05-21 fl Added mask; added rms, var, stddev attributes
+# 1997-08-05 fl Added median
+# 1998-07-05 hk Fixed integer overflow error
+#
+# Notes:
+# This class shows how to implement delayed evaluation of attributes.
+# To get a certain value, simply access the corresponding attribute.
+# The __getattr__ dispatcher takes care of the rest.
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import math
+import operator
+from functools import reduce
+
+
+class Stat:
+
+ def __init__(self, image_or_list, mask=None):
+ try:
+ if mask:
+ self.h = image_or_list.histogram(mask)
+ else:
+ self.h = image_or_list.histogram()
+ except AttributeError:
+ self.h = image_or_list # assume it to be a histogram list
+ if not isinstance(self.h, list):
+ raise TypeError("first argument must be image or list")
+ self.bands = list(range(len(self.h) // 256))
+
+ def __getattr__(self, id):
+ "Calculate missing attribute"
+ if id[:4] == "_get":
+ raise AttributeError(id)
+ # calculate missing attribute
+ v = getattr(self, "_get" + id)()
+ setattr(self, id, v)
+ return v
+
+ def _getextrema(self):
+ "Get min/max values for each band in the image"
+
+ def minmax(histogram):
+ n = 255
+ x = 0
+ for i in range(256):
+ if histogram[i]:
+ n = min(n, i)
+ x = max(x, i)
+ return n, x # returns (255, 0) if there's no data in the histogram
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ v.append(minmax(self.h[i:]))
+ return v
+
+ def _getcount(self):
+ "Get total number of pixels in each layer"
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ v.append(reduce(operator.add, self.h[i:i+256]))
+ return v
+
+ def _getsum(self):
+ "Get sum of all pixels in each layer"
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ sum = 0.0
+ for j in range(256):
+ sum += j * self.h[i + j]
+ v.append(sum)
+ return v
+
+ def _getsum2(self):
+ "Get squared sum of all pixels in each layer"
+
+ v = []
+ for i in range(0, len(self.h), 256):
+ sum2 = 0.0
+ for j in range(256):
+ sum2 += (j ** 2) * float(self.h[i + j])
+ v.append(sum2)
+ return v
+
+ def _getmean(self):
+ "Get average pixel level for each layer"
+
+ v = []
+ for i in self.bands:
+ v.append(self.sum[i] / self.count[i])
+ return v
+
+ def _getmedian(self):
+ "Get median pixel level for each layer"
+
+ v = []
+ for i in self.bands:
+ s = 0
+ l = self.count[i]//2
+ b = i * 256
+ for j in range(256):
+ s = s + self.h[b+j]
+ if s > l:
+ break
+ v.append(j)
+ return v
+
+ def _getrms(self):
+ "Get RMS for each layer"
+
+ v = []
+ for i in self.bands:
+ v.append(math.sqrt(self.sum2[i] / self.count[i]))
+ return v
+
+ def _getvar(self):
+ "Get variance for each layer"
+
+ v = []
+ for i in self.bands:
+ n = self.count[i]
+ v.append((self.sum2[i]-(self.sum[i]**2.0)/n)/n)
+ return v
+
+ def _getstddev(self):
+ "Get standard deviation for each layer"
+
+ v = []
+ for i in self.bands:
+ v.append(math.sqrt(self.var[i]))
+ return v
+
+Global = Stat # compatibility
diff --git a/lib/Python/Lib/PIL/ImageTk.py b/lib/Python/Lib/PIL/ImageTk.py
new file mode 100644
index 000000000..5fb5ecff3
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageTk.py
@@ -0,0 +1,292 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# a Tk display interface
+#
+# History:
+# 96-04-08 fl Created
+# 96-09-06 fl Added getimage method
+# 96-11-01 fl Rewritten, removed image attribute and crop method
+# 97-05-09 fl Use PyImagingPaste method instead of image type
+# 97-05-12 fl Minor tweaks to match the IFUNC95 interface
+# 97-05-17 fl Support the "pilbitmap" booster patch
+# 97-06-05 fl Added file= and data= argument to image constructors
+# 98-03-09 fl Added width and height methods to Image classes
+# 98-07-02 fl Use default mode for "P" images without palette attribute
+# 98-07-02 fl Explicitly destroy Tkinter image objects
+# 99-07-24 fl Support multiple Tk interpreters (from Greg Couch)
+# 99-07-26 fl Automatically hook into Tkinter (if possible)
+# 99-08-15 fl Hook uses _imagingtk instead of _imaging
+#
+# Copyright (c) 1997-1999 by Secret Labs AB
+# Copyright (c) 1996-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+try:
+ import tkinter
+except ImportError:
+ import Tkinter
+ tkinter = Tkinter
+ del Tkinter
+
+from PIL import Image
+
+
+# --------------------------------------------------------------------
+# Check for Tkinter interface hooks
+
+_pilbitmap_ok = None
+
+
+def _pilbitmap_check():
+ global _pilbitmap_ok
+ if _pilbitmap_ok is None:
+ try:
+ im = Image.new("1", (1, 1))
+ tkinter.BitmapImage(data="PIL:%d" % im.im.id)
+ _pilbitmap_ok = 1
+ except tkinter.TclError:
+ _pilbitmap_ok = 0
+ return _pilbitmap_ok
+
+
+# --------------------------------------------------------------------
+# PhotoImage
+
+class PhotoImage:
+ """
+ A Tkinter-compatible photo image. This can be used
+ everywhere Tkinter expects an image object. If the image is an RGBA
+ image, pixels having alpha 0 are treated as transparent.
+
+ The constructor takes either a PIL image, or a mode and a size.
+ Alternatively, you can use the **file** or **data** options to initialize
+ the photo image object.
+
+ :param image: Either a PIL image, or a mode string. If a mode string is
+ used, a size must also be given.
+ :param size: If the first argument is a mode string, this defines the size
+ of the image.
+ :keyword file: A filename to load the image from (using
+ ``Image.open(file)``).
+ :keyword data: An 8-bit string containing image data (as loaded from an
+ image file).
+ """
+
+ def __init__(self, image=None, size=None, **kw):
+
+ # Tk compatibility: file or data
+ if image is None:
+ if "file" in kw:
+ image = Image.open(kw["file"])
+ del kw["file"]
+ elif "data" in kw:
+ from io import BytesIO
+ image = Image.open(BytesIO(kw["data"]))
+ del kw["data"]
+
+ if hasattr(image, "mode") and hasattr(image, "size"):
+ # got an image instead of a mode
+ mode = image.mode
+ if mode == "P":
+ # palette mapped data
+ image.load()
+ try:
+ mode = image.palette.mode
+ except AttributeError:
+ mode = "RGB" # default
+ size = image.size
+ kw["width"], kw["height"] = size
+ else:
+ mode = image
+ image = None
+
+ if mode not in ["1", "L", "RGB", "RGBA"]:
+ mode = Image.getmodebase(mode)
+
+ self.__mode = mode
+ self.__size = size
+ self.__photo = tkinter.PhotoImage(**kw)
+ self.tk = self.__photo.tk
+ if image:
+ self.paste(image)
+
+ def __del__(self):
+ name = self.__photo.name
+ self.__photo.name = None
+ try:
+ self.__photo.tk.call("image", "delete", name)
+ except:
+ pass # ignore internal errors
+
+ def __str__(self):
+ """
+ Get the Tkinter photo image identifier. This method is automatically
+ called by Tkinter whenever a PhotoImage object is passed to a Tkinter
+ method.
+
+ :return: A Tkinter photo image identifier (a string).
+ """
+ return str(self.__photo)
+
+ def width(self):
+ """
+ Get the width of the image.
+
+ :return: The width, in pixels.
+ """
+ return self.__size[0]
+
+ def height(self):
+ """
+ Get the height of the image.
+
+ :return: The height, in pixels.
+ """
+ return self.__size[1]
+
+ def paste(self, im, box=None):
+ """
+ Paste a PIL image into the photo image. Note that this can
+ be very slow if the photo image is displayed.
+
+ :param im: A PIL image. The size must match the target region. If the
+ mode does not match, the image is converted to the mode of
+ the bitmap image.
+ :param box: A 4-tuple defining the left, upper, right, and lower pixel
+ coordinate. If None is given instead of a tuple, all of
+ the image is assumed.
+ """
+
+ # convert to blittable
+ im.load()
+ image = im.im
+ if image.isblock() and im.mode == self.__mode:
+ block = image
+ else:
+ block = image.new_block(self.__mode, im.size)
+ image.convert2(block, image) # convert directly between buffers
+
+ tk = self.__photo.tk
+
+ try:
+ tk.call("PyImagingPhoto", self.__photo, block.id)
+ except tkinter.TclError:
+ # activate Tkinter hook
+ try:
+ from PIL import _imagingtk
+ try:
+ _imagingtk.tkinit(tk.interpaddr(), 1)
+ except AttributeError:
+ _imagingtk.tkinit(id(tk), 0)
+ tk.call("PyImagingPhoto", self.__photo, block.id)
+ except (ImportError, AttributeError, tkinter.TclError):
+ raise # configuration problem; cannot attach to Tkinter
+
+# --------------------------------------------------------------------
+# BitmapImage
+
+
+class BitmapImage:
+ """
+
+ A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
+ expects an image object.
+
+ The given image must have mode "1". Pixels having value 0 are treated as
+ transparent. Options, if any, are passed on to Tkinter. The most commonly
+ used option is **foreground**, which is used to specify the color for the
+ non-transparent parts. See the Tkinter documentation for information on
+ how to specify colours.
+
+ :param image: A PIL image.
+ """
+
+ def __init__(self, image=None, **kw):
+
+ # Tk compatibility: file or data
+ if image is None:
+ if "file" in kw:
+ image = Image.open(kw["file"])
+ del kw["file"]
+ elif "data" in kw:
+ from io import BytesIO
+ image = Image.open(BytesIO(kw["data"]))
+ del kw["data"]
+
+ self.__mode = image.mode
+ self.__size = image.size
+
+ if _pilbitmap_check():
+ # fast way (requires the pilbitmap booster patch)
+ image.load()
+ kw["data"] = "PIL:%d" % image.im.id
+ self.__im = image # must keep a reference
+ else:
+ # slow but safe way
+ kw["data"] = image.tobitmap()
+ self.__photo = tkinter.BitmapImage(**kw)
+
+ def __del__(self):
+ name = self.__photo.name
+ self.__photo.name = None
+ try:
+ self.__photo.tk.call("image", "delete", name)
+ except:
+ pass # ignore internal errors
+
+ def width(self):
+ """
+ Get the width of the image.
+
+ :return: The width, in pixels.
+ """
+ return self.__size[0]
+
+ def height(self):
+ """
+ Get the height of the image.
+
+ :return: The height, in pixels.
+ """
+ return self.__size[1]
+
+ def __str__(self):
+ """
+ Get the Tkinter bitmap image identifier. This method is automatically
+ called by Tkinter whenever a BitmapImage object is passed to a Tkinter
+ method.
+
+ :return: A Tkinter bitmap image identifier (a string).
+ """
+ return str(self.__photo)
+
+
+def getimage(photo):
+ """Copies the contents of a PhotoImage to a PIL image memory."""
+ photo.tk.call("PyImagingPhotoGet", photo)
+
+
+# --------------------------------------------------------------------
+# Helper for the Image.show method.
+
+def _show(image, title):
+
+ class UI(tkinter.Label):
+ def __init__(self, master, im):
+ if im.mode == "1":
+ self.image = BitmapImage(im, foreground="white", master=master)
+ else:
+ self.image = PhotoImage(im, master=master)
+ tkinter.Label.__init__(self, master, image=self.image,
+ bg="black", bd=0)
+
+ if not tkinter._default_root:
+ raise IOError("tkinter not initialized")
+ top = tkinter.Toplevel()
+ if title:
+ top.title(title)
+ UI(top, image).pack()
diff --git a/lib/Python/Lib/PIL/ImageTransform.py b/lib/Python/Lib/PIL/ImageTransform.py
new file mode 100644
index 000000000..81f90502c
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageTransform.py
@@ -0,0 +1,103 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# transform wrappers
+#
+# History:
+# 2002-04-08 fl Created
+#
+# Copyright (c) 2002 by Secret Labs AB
+# Copyright (c) 2002 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+
+
+class Transform(Image.ImageTransformHandler):
+ def __init__(self, data):
+ self.data = data
+
+ def getdata(self):
+ return self.method, self.data
+
+ def transform(self, size, image, **options):
+ # can be overridden
+ method, data = self.getdata()
+ return image.transform(size, method, data, **options)
+
+
+##
+# Define an affine image transform.
+# <p>
+# This function takes a 6-tuple (<i>a, b, c, d, e, f</i>) which
+# contain the first two rows from an affine transform matrix. For
+# each pixel (<i>x, y</i>) in the output image, the new value is
+# taken from a position (a <i>x</i> + b <i>y</i> + c,
+# d <i>x</i> + e <i>y</i> + f) in the input image, rounded to
+# nearest pixel.
+# <p>
+# This function can be used to scale, translate, rotate, and shear the
+# original image.
+#
+# @def AffineTransform(matrix)
+# @param matrix A 6-tuple (<i>a, b, c, d, e, f</i>) containing
+# the first two rows from an affine transform matrix.
+# @see Image#Image.transform
+
+
+class AffineTransform(Transform):
+ method = Image.AFFINE
+
+
+##
+# Define a transform to extract a subregion from an image.
+# <p>
+# Maps a rectangle (defined by two corners) from the image to a
+# rectangle of the given size. The resulting image will contain
+# data sampled from between the corners, such that (<i>x0, y0</i>)
+# in the input image will end up at (0,0) in the output image,
+# and (<i>x1, y1</i>) at <i>size</i>.
+# <p>
+# This method can be used to crop, stretch, shrink, or mirror an
+# arbitrary rectangle in the current image. It is slightly slower than
+# <b>crop</b>, but about as fast as a corresponding <b>resize</b>
+# operation.
+#
+# @def ExtentTransform(bbox)
+# @param bbox A 4-tuple (<i>x0, y0, x1, y1</i>) which specifies
+# two points in the input image's coordinate system.
+# @see Image#Image.transform
+
+class ExtentTransform(Transform):
+ method = Image.EXTENT
+
+
+##
+# Define an quad image transform.
+# <p>
+# Maps a quadrilateral (a region defined by four corners) from the
+# image to a rectangle of the given size.
+#
+# @def QuadTransform(xy)
+# @param xy An 8-tuple (<i>x0, y0, x1, y1, x2, y2, y3, y3</i>) which
+# contain the upper left, lower left, lower right, and upper right
+# corner of the source quadrilateral.
+# @see Image#Image.transform
+
+class QuadTransform(Transform):
+ method = Image.QUAD
+
+
+##
+# Define an mesh image transform. A mesh transform consists of one
+# or more individual quad transforms.
+#
+# @def MeshTransform(data)
+# @param data A list of (bbox, quad) tuples.
+# @see Image#Image.transform
+
+class MeshTransform(Transform):
+ method = Image.MESH
diff --git a/lib/Python/Lib/PIL/ImageWin.py b/lib/Python/Lib/PIL/ImageWin.py
new file mode 100644
index 000000000..300d118c9
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImageWin.py
@@ -0,0 +1,251 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# a Windows DIB display interface
+#
+# History:
+# 1996-05-20 fl Created
+# 1996-09-20 fl Fixed subregion exposure
+# 1997-09-21 fl Added draw primitive (for tzPrint)
+# 2003-05-21 fl Added experimental Window/ImageWindow classes
+# 2003-09-05 fl Added fromstring/tostring methods
+#
+# Copyright (c) Secret Labs AB 1997-2003.
+# Copyright (c) Fredrik Lundh 1996-2003.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import warnings
+from PIL import Image
+
+
+class HDC:
+ """
+ Wraps an HDC integer. The resulting object can be passed to the
+ :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
+ methods.
+ """
+ def __init__(self, dc):
+ self.dc = dc
+
+ def __int__(self):
+ return self.dc
+
+
+class HWND:
+ """
+ Wraps an HWND integer. The resulting object can be passed to the
+ :py:meth:`~PIL.ImageWin.Dib.draw` and :py:meth:`~PIL.ImageWin.Dib.expose`
+ methods, instead of a DC.
+ """
+ def __init__(self, wnd):
+ self.wnd = wnd
+
+ def __int__(self):
+ return self.wnd
+
+
+class Dib:
+ """
+ A Windows bitmap with the given mode and size. The mode can be one of "1",
+ "L", "P", or "RGB".
+
+ If the display requires a palette, this constructor creates a suitable
+ palette and associates it with the image. For an "L" image, 128 greylevels
+ are allocated. For an "RGB" image, a 6x6x6 colour cube is used, together
+ with 20 greylevels.
+
+ To make sure that palettes work properly under Windows, you must call the
+ **palette** method upon certain events from Windows.
+
+ :param image: Either a PIL image, or a mode string. If a mode string is
+ used, a size must also be given. The mode can be one of "1",
+ "L", "P", or "RGB".
+ :param size: If the first argument is a mode string, this
+ defines the size of the image.
+ """
+
+ def __init__(self, image, size=None):
+ if hasattr(image, "mode") and hasattr(image, "size"):
+ mode = image.mode
+ size = image.size
+ else:
+ mode = image
+ image = None
+ if mode not in ["1", "L", "P", "RGB"]:
+ mode = Image.getmodebase(mode)
+ self.image = Image.core.display(mode, size)
+ self.mode = mode
+ self.size = size
+ if image:
+ self.paste(image)
+
+ def expose(self, handle):
+ """
+ Copy the bitmap contents to a device context.
+
+ :param handle: Device context (HDC), cast to a Python integer, or an
+ HDC or HWND instance. In PythonWin, you can use the
+ :py:meth:`CDC.GetHandleAttrib` to get a suitable handle.
+ """
+ if isinstance(handle, HWND):
+ dc = self.image.getdc(handle)
+ try:
+ result = self.image.expose(dc)
+ finally:
+ self.image.releasedc(handle, dc)
+ else:
+ result = self.image.expose(handle)
+ return result
+
+ def draw(self, handle, dst, src=None):
+ """
+ Same as expose, but allows you to specify where to draw the image, and
+ what part of it to draw.
+
+ The destination and source areas are given as 4-tuple rectangles. If
+ the source is omitted, the entire image is copied. If the source and
+ the destination have different sizes, the image is resized as
+ necessary.
+ """
+ if not src:
+ src = (0, 0) + self.size
+ if isinstance(handle, HWND):
+ dc = self.image.getdc(handle)
+ try:
+ result = self.image.draw(dc, dst, src)
+ finally:
+ self.image.releasedc(handle, dc)
+ else:
+ result = self.image.draw(handle, dst, src)
+ return result
+
+ def query_palette(self, handle):
+ """
+ Installs the palette associated with the image in the given device
+ context.
+
+ This method should be called upon **QUERYNEWPALETTE** and
+ **PALETTECHANGED** events from Windows. If this method returns a
+ non-zero value, one or more display palette entries were changed, and
+ the image should be redrawn.
+
+ :param handle: Device context (HDC), cast to a Python integer, or an
+ HDC or HWND instance.
+ :return: A true value if one or more entries were changed (this
+ indicates that the image should be redrawn).
+ """
+ if isinstance(handle, HWND):
+ handle = self.image.getdc(handle)
+ try:
+ result = self.image.query_palette(handle)
+ finally:
+ self.image.releasedc(handle, handle)
+ else:
+ result = self.image.query_palette(handle)
+ return result
+
+ def paste(self, im, box=None):
+ """
+ Paste a PIL image into the bitmap image.
+
+ :param im: A PIL image. The size must match the target region.
+ If the mode does not match, the image is converted to the
+ mode of the bitmap image.
+ :param box: A 4-tuple defining the left, upper, right, and
+ lower pixel coordinate. If None is given instead of a
+ tuple, all of the image is assumed.
+ """
+ im.load()
+ if self.mode != im.mode:
+ im = im.convert(self.mode)
+ if box:
+ self.image.paste(im.im, box)
+ else:
+ self.image.paste(im.im)
+
+ def frombytes(self, buffer):
+ """
+ Load display memory contents from byte data.
+
+ :param buffer: A buffer containing display data (usually
+ data returned from <b>tobytes</b>)
+ """
+ return self.image.frombytes(buffer)
+
+ def tobytes(self):
+ """
+ Copy display memory contents to bytes object.
+
+ :return: A bytes object containing display data.
+ """
+ return self.image.tobytes()
+
+ ##
+ # Deprecated aliases to frombytes & tobytes.
+
+ def fromstring(self, *args, **kw):
+ warnings.warn(
+ 'fromstring() is deprecated. Please call frombytes() instead.',
+ DeprecationWarning,
+ stacklevel=2
+ )
+ return self.frombytes(*args, **kw)
+
+ def tostring(self):
+ warnings.warn(
+ 'tostring() is deprecated. Please call tobytes() instead.',
+ DeprecationWarning,
+ stacklevel=2
+ )
+ return self.tobytes()
+
+
+##
+# Create a Window with the given title size.
+
+class Window:
+
+ def __init__(self, title="PIL", width=None, height=None):
+ self.hwnd = Image.core.createwindow(
+ title, self.__dispatcher, width or 0, height or 0
+ )
+
+ def __dispatcher(self, action, *args):
+ return getattr(self, "ui_handle_" + action)(*args)
+
+ def ui_handle_clear(self, dc, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_damage(self, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_destroy(self):
+ pass
+
+ def ui_handle_repair(self, dc, x0, y0, x1, y1):
+ pass
+
+ def ui_handle_resize(self, width, height):
+ pass
+
+ def mainloop(self):
+ Image.core.eventloop()
+
+
+##
+# Create an image window which displays the given image.
+
+class ImageWindow(Window):
+
+ def __init__(self, image, title="PIL"):
+ if not isinstance(image, Dib):
+ image = Dib(image)
+ self.image = image
+ width, height = image.size
+ Window.__init__(self, title, width=width, height=height)
+
+ def ui_handle_repair(self, dc, x0, y0, x1, y1):
+ self.image.draw(dc, (x0, y0, x1, y1))
diff --git a/lib/Python/Lib/PIL/ImtImagePlugin.py b/lib/Python/Lib/PIL/ImtImagePlugin.py
new file mode 100644
index 000000000..f512eb801
--- /dev/null
+++ b/lib/Python/Lib/PIL/ImtImagePlugin.py
@@ -0,0 +1,94 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# IM Tools support for PIL
+#
+# history:
+# 1996-05-27 fl Created (read 8-bit images only)
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.2)
+#
+# Copyright (c) Secret Labs AB 1997-2001.
+# Copyright (c) Fredrik Lundh 1996-2001.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+import re
+
+from PIL import Image, ImageFile
+
+#
+# --------------------------------------------------------------------
+
+field = re.compile(br"([a-z]*) ([^ \r\n]*)")
+
+
+##
+# Image plugin for IM Tools images.
+
+class ImtImageFile(ImageFile.ImageFile):
+
+ format = "IMT"
+ format_description = "IM Tools"
+
+ def _open(self):
+
+ # Quick rejection: if there's not a LF among the first
+ # 100 bytes, this is (probably) not a text header.
+
+ if b"\n" not in self.fp.read(100):
+ raise SyntaxError("not an IM file")
+ self.fp.seek(0)
+
+ xsize = ysize = 0
+
+ while True:
+
+ s = self.fp.read(1)
+ if not s:
+ break
+
+ if s == b'\x0C':
+
+ # image data begins
+ self.tile = [("raw", (0, 0)+self.size,
+ self.fp.tell(),
+ (self.mode, 0, 1))]
+
+ break
+
+ else:
+
+ # read key/value pair
+ # FIXME: dangerous, may read whole file
+ s = s + self.fp.readline()
+ if len(s) == 1 or len(s) > 100:
+ break
+ if s[0] == b"*":
+ continue # comment
+
+ m = field.match(s)
+ if not m:
+ break
+ k, v = m.group(1, 2)
+ if k == "width":
+ xsize = int(v)
+ self.size = xsize, ysize
+ elif k == "height":
+ ysize = int(v)
+ self.size = xsize, ysize
+ elif k == "pixel" and v == "n8":
+ self.mode = "L"
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("IMT", ImtImageFile)
+
+#
+# no extension registered (".im" is simply too common)
diff --git a/lib/Python/Lib/PIL/IptcImagePlugin.py b/lib/Python/Lib/PIL/IptcImagePlugin.py
new file mode 100644
index 000000000..dc8607591
--- /dev/null
+++ b/lib/Python/Lib/PIL/IptcImagePlugin.py
@@ -0,0 +1,268 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# IPTC/NAA file handling
+#
+# history:
+# 1995-10-01 fl Created
+# 1998-03-09 fl Cleaned up and added to PIL
+# 2002-06-18 fl Added getiptcinfo helper
+#
+# Copyright (c) Secret Labs AB 1997-2002.
+# Copyright (c) Fredrik Lundh 1995.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from __future__ import print_function
+
+__version__ = "0.3"
+
+
+from PIL import Image, ImageFile, _binary
+import os
+import tempfile
+
+i8 = _binary.i8
+i16 = _binary.i16be
+i32 = _binary.i32be
+o8 = _binary.o8
+
+COMPRESSION = {
+ 1: "raw",
+ 5: "jpeg"
+}
+
+PAD = o8(0) * 4
+
+
+#
+# Helpers
+
+def i(c):
+ return i32((PAD + c)[-4:])
+
+
+def dump(c):
+ for i in c:
+ print("%02x" % i8(i), end=' ')
+ print()
+
+
+##
+# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
+# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
+
+class IptcImageFile(ImageFile.ImageFile):
+
+ format = "IPTC"
+ format_description = "IPTC/NAA"
+
+ def getint(self, key):
+ return i(self.info[key])
+
+ def field(self):
+ #
+ # get a IPTC field header
+ s = self.fp.read(5)
+ if not len(s):
+ return None, 0
+
+ tag = i8(s[1]), i8(s[2])
+
+ # syntax
+ if i8(s[0]) != 0x1C or tag[0] < 1 or tag[0] > 9:
+ raise SyntaxError("invalid IPTC/NAA file")
+
+ # field size
+ size = i8(s[3])
+ if size > 132:
+ raise IOError("illegal field length in IPTC/NAA file")
+ elif size == 128:
+ size = 0
+ elif size > 128:
+ size = i(self.fp.read(size-128))
+ else:
+ size = i16(s[3:])
+
+ return tag, size
+
+ def _open(self):
+
+ # load descriptive fields
+ while True:
+ offset = self.fp.tell()
+ tag, size = self.field()
+ if not tag or tag == (8, 10):
+ break
+ if size:
+ tagdata = self.fp.read(size)
+ else:
+ tagdata = None
+ if tag in list(self.info.keys()):
+ if isinstance(self.info[tag], list):
+ self.info[tag].append(tagdata)
+ else:
+ self.info[tag] = [self.info[tag], tagdata]
+ else:
+ self.info[tag] = tagdata
+
+ # print tag, self.info[tag]
+
+ # mode
+ layers = i8(self.info[(3, 60)][0])
+ component = i8(self.info[(3, 60)][1])
+ if (3, 65) in self.info:
+ id = i8(self.info[(3, 65)][0])-1
+ else:
+ id = 0
+ if layers == 1 and not component:
+ self.mode = "L"
+ elif layers == 3 and component:
+ self.mode = "RGB"[id]
+ elif layers == 4 and component:
+ self.mode = "CMYK"[id]
+
+ # size
+ self.size = self.getint((3, 20)), self.getint((3, 30))
+
+ # compression
+ try:
+ compression = COMPRESSION[self.getint((3, 120))]
+ except KeyError:
+ raise IOError("Unknown IPTC image compression")
+
+ # tile
+ if tag == (8, 10):
+ self.tile = [("iptc", (compression, offset),
+ (0, 0, self.size[0], self.size[1]))]
+
+ def load(self):
+
+ if len(self.tile) != 1 or self.tile[0][0] != "iptc":
+ return ImageFile.ImageFile.load(self)
+
+ type, tile, box = self.tile[0]
+
+ encoding, offset = tile
+
+ self.fp.seek(offset)
+
+ # Copy image data to temporary file
+ o_fd, outfile = tempfile.mkstemp(text=False)
+ o = os.fdopen(o_fd)
+ if encoding == "raw":
+ # To simplify access to the extracted file,
+ # prepend a PPM header
+ o.write("P5\n%d %d\n255\n" % self.size)
+ while True:
+ type, size = self.field()
+ if type != (8, 10):
+ break
+ while size > 0:
+ s = self.fp.read(min(size, 8192))
+ if not s:
+ break
+ o.write(s)
+ size -= len(s)
+ o.close()
+
+ try:
+ try:
+ # fast
+ self.im = Image.core.open_ppm(outfile)
+ except:
+ # slightly slower
+ im = Image.open(outfile)
+ im.load()
+ self.im = im.im
+ finally:
+ try:
+ os.unlink(outfile)
+ except:
+ pass
+
+
+Image.register_open("IPTC", IptcImageFile)
+
+Image.register_extension("IPTC", ".iim")
+
+
+##
+# Get IPTC information from TIFF, JPEG, or IPTC file.
+#
+# @param im An image containing IPTC data.
+# @return A dictionary containing IPTC information, or None if
+# no IPTC information block was found.
+
+def getiptcinfo(im):
+
+ from PIL import TiffImagePlugin, JpegImagePlugin
+ import io
+
+ data = None
+
+ if isinstance(im, IptcImageFile):
+ # return info dictionary right away
+ return im.info
+
+ elif isinstance(im, JpegImagePlugin.JpegImageFile):
+ # extract the IPTC/NAA resource
+ try:
+ app = im.app["APP13"]
+ if app[:14] == b"Photoshop 3.0\x00":
+ app = app[14:]
+ # parse the image resource block
+ offset = 0
+ while app[offset:offset+4] == b"8BIM":
+ offset += 4
+ # resource code
+ code = JpegImagePlugin.i16(app, offset)
+ offset += 2
+ # resource name (usually empty)
+ name_len = i8(app[offset])
+ name = app[offset+1:offset+1+name_len]
+ offset = 1 + offset + name_len
+ if offset & 1:
+ offset += 1
+ # resource data block
+ size = JpegImagePlugin.i32(app, offset)
+ offset += 4
+ if code == 0x0404:
+ # 0x0404 contains IPTC/NAA data
+ data = app[offset:offset+size]
+ break
+ offset = offset + size
+ if offset & 1:
+ offset += 1
+ except (AttributeError, KeyError):
+ pass
+
+ elif isinstance(im, TiffImagePlugin.TiffImageFile):
+ # get raw data from the IPTC/NAA tag (PhotoShop tags the data
+ # as 4-byte integers, so we cannot use the get method...)
+ try:
+ data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
+ except (AttributeError, KeyError):
+ pass
+
+ if data is None:
+ return None # no properties
+
+ # create an IptcImagePlugin object without initializing it
+ class FakeImage:
+ pass
+ im = FakeImage()
+ im.__class__ = IptcImageFile
+
+ # parse the IPTC information chunk
+ im.info = {}
+ im.fp = io.BytesIO(data)
+
+ try:
+ im._open()
+ except (IndexError, KeyError):
+ pass # expected failure
+
+ return im.info
diff --git a/lib/Python/Lib/PIL/Jpeg2KImagePlugin.py b/lib/Python/Lib/PIL/Jpeg2KImagePlugin.py
new file mode 100644
index 000000000..ed3e1530a
--- /dev/null
+++ b/lib/Python/Lib/PIL/Jpeg2KImagePlugin.py
@@ -0,0 +1,276 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# JPEG2000 file handling
+#
+# History:
+# 2014-03-12 ajh Created
+#
+# Copyright (c) 2014 Coriolis Systems Limited
+# Copyright (c) 2014 Alastair Houghton
+#
+# See the README file for information on usage and redistribution.
+#
+from PIL import Image, ImageFile
+import struct
+import os
+import io
+
+__version__ = "0.1"
+
+
+def _parse_codestream(fp):
+ """Parse the JPEG 2000 codestream to extract the size and component
+ count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
+
+ hdr = fp.read(2)
+ lsiz = struct.unpack('>H', hdr)[0]
+ siz = hdr + fp.read(lsiz - 2)
+ lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
+ xtosiz, ytosiz, csiz \
+ = struct.unpack('>HHIIIIIIIIH', siz[:38])
+ ssiz = [None]*csiz
+ xrsiz = [None]*csiz
+ yrsiz = [None]*csiz
+ for i in range(csiz):
+ ssiz[i], xrsiz[i], yrsiz[i] \
+ = struct.unpack('>BBB', siz[36 + 3 * i:39 + 3 * i])
+
+ size = (xsiz - xosiz, ysiz - yosiz)
+ if csiz == 1:
+ if (yrsiz[0] & 0x7f) > 8:
+ mode = 'I;16'
+ else:
+ mode = 'L'
+ elif csiz == 2:
+ mode = 'LA'
+ elif csiz == 3:
+ mode = 'RGB'
+ elif csiz == 4:
+ mode = 'RGBA'
+ else:
+ mode = None
+
+ return (size, mode)
+
+
+def _parse_jp2_header(fp):
+ """Parse the JP2 header box to extract size, component count and
+ color space information, returning a PIL (size, mode) tuple."""
+
+ # Find the JP2 header box
+ header = None
+ while True:
+ lbox, tbox = struct.unpack('>I4s', fp.read(8))
+ if lbox == 1:
+ lbox = struct.unpack('>Q', fp.read(8))[0]
+ hlen = 16
+ else:
+ hlen = 8
+
+ if lbox < hlen:
+ raise SyntaxError('Invalid JP2 header length')
+
+ if tbox == b'jp2h':
+ header = fp.read(lbox - hlen)
+ break
+ else:
+ fp.seek(lbox - hlen, os.SEEK_CUR)
+
+ if header is None:
+ raise SyntaxError('could not find JP2 header')
+
+ size = None
+ mode = None
+ bpc = None
+
+ hio = io.BytesIO(header)
+ while True:
+ lbox, tbox = struct.unpack('>I4s', hio.read(8))
+ if lbox == 1:
+ lbox = struct.unpack('>Q', hio.read(8))[0]
+ hlen = 16
+ else:
+ hlen = 8
+
+ content = hio.read(lbox - hlen)
+
+ if tbox == b'ihdr':
+ height, width, nc, bpc, c, unkc, ipr \
+ = struct.unpack('>IIHBBBB', content)
+ size = (width, height)
+ if unkc:
+ if nc == 1 and (bpc & 0x7f) > 8:
+ mode = 'I;16'
+ elif nc == 1:
+ mode = 'L'
+ elif nc == 2:
+ mode = 'LA'
+ elif nc == 3:
+ mode = 'RGB'
+ elif nc == 4:
+ mode = 'RGBA'
+ break
+ elif tbox == b'colr':
+ meth, prec, approx = struct.unpack('>BBB', content[:3])
+ if meth == 1:
+ cs = struct.unpack('>I', content[3:7])[0]
+ if cs == 16: # sRGB
+ if nc == 1 and (bpc & 0x7f) > 8:
+ mode = 'I;16'
+ elif nc == 1:
+ mode = 'L'
+ elif nc == 3:
+ mode = 'RGB'
+ elif nc == 4:
+ mode = 'RGBA'
+ break
+ elif cs == 17: # grayscale
+ if nc == 1 and (bpc & 0x7f) > 8:
+ mode = 'I;16'
+ elif nc == 1:
+ mode = 'L'
+ elif nc == 2:
+ mode = 'LA'
+ break
+ elif cs == 18: # sYCC
+ if nc == 3:
+ mode = 'RGB'
+ elif nc == 4:
+ mode = 'RGBA'
+ break
+
+ return (size, mode)
+
+##
+# Image plugin for JPEG2000 images.
+
+
+class Jpeg2KImageFile(ImageFile.ImageFile):
+ format = "JPEG2000"
+ format_description = "JPEG 2000 (ISO 15444)"
+
+ def _open(self):
+ sig = self.fp.read(4)
+ if sig == b'\xff\x4f\xff\x51':
+ self.codec = "j2k"
+ self.size, self.mode = _parse_codestream(self.fp)
+ else:
+ sig = sig + self.fp.read(8)
+
+ if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
+ self.codec = "jp2"
+ self.size, self.mode = _parse_jp2_header(self.fp)
+ else:
+ raise SyntaxError('not a JPEG 2000 file')
+
+ if self.size is None or self.mode is None:
+ raise SyntaxError('unable to determine size/mode')
+
+ self.reduce = 0
+ self.layers = 0
+
+ fd = -1
+ length = -1
+
+ try:
+ fd = self.fp.fileno()
+ length = os.fstat(fd).st_size
+ except:
+ fd = -1
+ try:
+ pos = self.fp.tell()
+ self.fp.seek(0, 2)
+ length = self.fp.tell()
+ self.fp.seek(pos, 0)
+ except:
+ length = -1
+
+ self.tile = [('jpeg2k', (0, 0) + self.size, 0,
+ (self.codec, self.reduce, self.layers, fd, length))]
+
+ def load(self):
+ if self.reduce:
+ power = 1 << self.reduce
+ adjust = power >> 1
+ self.size = (int((self.size[0] + adjust) / power),
+ int((self.size[1] + adjust) / power))
+
+ if self.tile:
+ # Update the reduce and layers settings
+ t = self.tile[0]
+ t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
+ self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
+
+ ImageFile.ImageFile.load(self)
+
+
+def _accept(prefix):
+ return (prefix[:4] == b'\xff\x4f\xff\x51' or
+ prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
+
+
+# ------------------------------------------------------------
+# Save support
+
+def _save(im, fp, filename):
+ if filename.endswith('.j2k'):
+ kind = 'j2k'
+ else:
+ kind = 'jp2'
+
+ # Get the keyword arguments
+ info = im.encoderinfo
+
+ offset = info.get('offset', None)
+ tile_offset = info.get('tile_offset', None)
+ tile_size = info.get('tile_size', None)
+ quality_mode = info.get('quality_mode', 'rates')
+ quality_layers = info.get('quality_layers', None)
+ num_resolutions = info.get('num_resolutions', 0)
+ cblk_size = info.get('codeblock_size', None)
+ precinct_size = info.get('precinct_size', None)
+ irreversible = info.get('irreversible', False)
+ progression = info.get('progression', 'LRCP')
+ cinema_mode = info.get('cinema_mode', 'no')
+ fd = -1
+
+ if hasattr(fp, "fileno"):
+ try:
+ fd = fp.fileno()
+ except:
+ fd = -1
+
+ im.encoderconfig = (
+ offset,
+ tile_offset,
+ tile_size,
+ quality_mode,
+ quality_layers,
+ num_resolutions,
+ cblk_size,
+ precinct_size,
+ irreversible,
+ progression,
+ cinema_mode,
+ fd
+ )
+
+ ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
+
+# ------------------------------------------------------------
+# Registry stuff
+
+Image.register_open('JPEG2000', Jpeg2KImageFile, _accept)
+Image.register_save('JPEG2000', _save)
+
+Image.register_extension('JPEG2000', '.jp2')
+Image.register_extension('JPEG2000', '.j2k')
+Image.register_extension('JPEG2000', '.jpc')
+Image.register_extension('JPEG2000', '.jpf')
+Image.register_extension('JPEG2000', '.jpx')
+Image.register_extension('JPEG2000', '.j2c')
+
+Image.register_mime('JPEG2000', 'image/jp2')
+Image.register_mime('JPEG2000', 'image/jpx')
diff --git a/lib/Python/Lib/PIL/JpegImagePlugin.py b/lib/Python/Lib/PIL/JpegImagePlugin.py
new file mode 100644
index 000000000..8c20f5863
--- /dev/null
+++ b/lib/Python/Lib/PIL/JpegImagePlugin.py
@@ -0,0 +1,739 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# JPEG (JFIF) file handling
+#
+# See "Digital Compression and Coding of Continous-Tone Still Images,
+# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
+#
+# History:
+# 1995-09-09 fl Created
+# 1995-09-13 fl Added full parser
+# 1996-03-25 fl Added hack to use the IJG command line utilities
+# 1996-05-05 fl Workaround Photoshop 2.5 CMYK polarity bug
+# 1996-05-28 fl Added draft support, JFIF version (0.1)
+# 1996-12-30 fl Added encoder options, added progression property (0.2)
+# 1997-08-27 fl Save mode 1 images as BW (0.3)
+# 1998-07-12 fl Added YCbCr to draft and save methods (0.4)
+# 1998-10-19 fl Don't hang on files using 16-bit DQT's (0.4.1)
+# 2001-04-16 fl Extract DPI settings from JFIF files (0.4.2)
+# 2002-07-01 fl Skip pad bytes before markers; identify Exif files (0.4.3)
+# 2003-04-25 fl Added experimental EXIF decoder (0.5)
+# 2003-06-06 fl Added experimental EXIF GPSinfo decoder
+# 2003-09-13 fl Extract COM markers
+# 2009-09-06 fl Added icc_profile support (from Florian Hoech)
+# 2009-03-06 fl Changed CMYK handling; always use Adobe polarity (0.6)
+# 2009-03-08 fl Added subsampling support (from Justin Huff).
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-1996 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.6"
+
+import array
+import struct
+import io
+from struct import unpack
+from PIL import Image, ImageFile, TiffImagePlugin, _binary
+from PIL.JpegPresets import presets
+from PIL._util import isStringType
+
+i8 = _binary.i8
+o8 = _binary.o8
+i16 = _binary.i16be
+i32 = _binary.i32be
+
+
+#
+# Parser
+
+def Skip(self, marker):
+ n = i16(self.fp.read(2))-2
+ ImageFile._safe_read(self.fp, n)
+
+
+def APP(self, marker):
+ #
+ # Application marker. Store these in the APP dictionary.
+ # Also look for well-known application markers.
+
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+
+ app = "APP%d" % (marker & 15)
+
+ self.app[app] = s # compatibility
+ self.applist.append((app, s))
+
+ if marker == 0xFFE0 and s[:4] == b"JFIF":
+ # extract JFIF information
+ self.info["jfif"] = version = i16(s, 5) # version
+ self.info["jfif_version"] = divmod(version, 256)
+ # extract JFIF properties
+ try:
+ jfif_unit = i8(s[7])
+ jfif_density = i16(s, 8), i16(s, 10)
+ except:
+ pass
+ else:
+ if jfif_unit == 1:
+ self.info["dpi"] = jfif_density
+ self.info["jfif_unit"] = jfif_unit
+ self.info["jfif_density"] = jfif_density
+ elif marker == 0xFFE1 and s[:5] == b"Exif\0":
+ # extract Exif information (incomplete)
+ self.info["exif"] = s # FIXME: value will change
+ elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
+ # extract FlashPix information (incomplete)
+ self.info["flashpix"] = s # FIXME: value will change
+ elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
+ # Since an ICC profile can be larger than the maximum size of
+ # a JPEG marker (64K), we need provisions to split it into
+ # multiple markers. The format defined by the ICC specifies
+ # one or more APP2 markers containing the following data:
+ # Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
+ # Marker sequence number 1, 2, etc (1 byte)
+ # Number of markers Total of APP2's used (1 byte)
+ # Profile data (remainder of APP2 data)
+ # Decoders should use the marker sequence numbers to
+ # reassemble the profile, rather than assuming that the APP2
+ # markers appear in the correct sequence.
+ self.icclist.append(s)
+ elif marker == 0xFFEE and s[:5] == b"Adobe":
+ self.info["adobe"] = i16(s, 5)
+ # extract Adobe custom properties
+ try:
+ adobe_transform = i8(s[1])
+ except:
+ pass
+ else:
+ self.info["adobe_transform"] = adobe_transform
+ elif marker == 0xFFE2 and s[:4] == b"MPF\0":
+ # extract MPO information
+ self.info["mp"] = s[4:]
+ # offset is current location minus buffer size
+ # plus constant header size
+ self.info["mpoffset"] = self.fp.tell() - n + 4
+
+
+def COM(self, marker):
+ #
+ # Comment marker. Store these in the APP dictionary.
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+
+ self.app["COM"] = s # compatibility
+ self.applist.append(("COM", s))
+
+
+def SOF(self, marker):
+ #
+ # Start of frame marker. Defines the size and mode of the
+ # image. JPEG is colour blind, so we use some simple
+ # heuristics to map the number of layers to an appropriate
+ # mode. Note that this could be made a bit brighter, by
+ # looking for JFIF and Adobe APP markers.
+
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+ self.size = i16(s[3:]), i16(s[1:])
+
+ self.bits = i8(s[0])
+ if self.bits != 8:
+ raise SyntaxError("cannot handle %d-bit layers" % self.bits)
+
+ self.layers = i8(s[5])
+ if self.layers == 1:
+ self.mode = "L"
+ elif self.layers == 3:
+ self.mode = "RGB"
+ elif self.layers == 4:
+ self.mode = "CMYK"
+ else:
+ raise SyntaxError("cannot handle %d-layer images" % self.layers)
+
+ if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:
+ self.info["progressive"] = self.info["progression"] = 1
+
+ if self.icclist:
+ # fixup icc profile
+ self.icclist.sort() # sort by sequence number
+ if i8(self.icclist[0][13]) == len(self.icclist):
+ profile = []
+ for p in self.icclist:
+ profile.append(p[14:])
+ icc_profile = b"".join(profile)
+ else:
+ icc_profile = None # wrong number of fragments
+ self.info["icc_profile"] = icc_profile
+ self.icclist = None
+
+ for i in range(6, len(s), 3):
+ t = s[i:i+3]
+ # 4-tuples: id, vsamp, hsamp, qtable
+ self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2])))
+
+
+def DQT(self, marker):
+ #
+ # Define quantization table. Support baseline 8-bit tables
+ # only. Note that there might be more than one table in
+ # each marker.
+
+ # FIXME: The quantization tables can be used to estimate the
+ # compression quality.
+
+ n = i16(self.fp.read(2))-2
+ s = ImageFile._safe_read(self.fp, n)
+ while len(s):
+ if len(s) < 65:
+ raise SyntaxError("bad quantization table marker")
+ v = i8(s[0])
+ if v//16 == 0:
+ self.quantization[v & 15] = array.array("b", s[1:65])
+ s = s[65:]
+ else:
+ return # FIXME: add code to read 16-bit tables!
+ # raise SyntaxError, "bad quantization table element size"
+
+
+#
+# JPEG marker table
+
+MARKER = {
+ 0xFFC0: ("SOF0", "Baseline DCT", SOF),
+ 0xFFC1: ("SOF1", "Extended Sequential DCT", SOF),
+ 0xFFC2: ("SOF2", "Progressive DCT", SOF),
+ 0xFFC3: ("SOF3", "Spatial lossless", SOF),
+ 0xFFC4: ("DHT", "Define Huffman table", Skip),
+ 0xFFC5: ("SOF5", "Differential sequential DCT", SOF),
+ 0xFFC6: ("SOF6", "Differential progressive DCT", SOF),
+ 0xFFC7: ("SOF7", "Differential spatial", SOF),
+ 0xFFC8: ("JPG", "Extension", None),
+ 0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF),
+ 0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF),
+ 0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF),
+ 0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip),
+ 0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF),
+ 0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF),
+ 0xFFCF: ("SOF15", "Differential spatial (AC)", SOF),
+ 0xFFD0: ("RST0", "Restart 0", None),
+ 0xFFD1: ("RST1", "Restart 1", None),
+ 0xFFD2: ("RST2", "Restart 2", None),
+ 0xFFD3: ("RST3", "Restart 3", None),
+ 0xFFD4: ("RST4", "Restart 4", None),
+ 0xFFD5: ("RST5", "Restart 5", None),
+ 0xFFD6: ("RST6", "Restart 6", None),
+ 0xFFD7: ("RST7", "Restart 7", None),
+ 0xFFD8: ("SOI", "Start of image", None),
+ 0xFFD9: ("EOI", "End of image", None),
+ 0xFFDA: ("SOS", "Start of scan", Skip),
+ 0xFFDB: ("DQT", "Define quantization table", DQT),
+ 0xFFDC: ("DNL", "Define number of lines", Skip),
+ 0xFFDD: ("DRI", "Define restart interval", Skip),
+ 0xFFDE: ("DHP", "Define hierarchical progression", SOF),
+ 0xFFDF: ("EXP", "Expand reference component", Skip),
+ 0xFFE0: ("APP0", "Application segment 0", APP),
+ 0xFFE1: ("APP1", "Application segment 1", APP),
+ 0xFFE2: ("APP2", "Application segment 2", APP),
+ 0xFFE3: ("APP3", "Application segment 3", APP),
+ 0xFFE4: ("APP4", "Application segment 4", APP),
+ 0xFFE5: ("APP5", "Application segment 5", APP),
+ 0xFFE6: ("APP6", "Application segment 6", APP),
+ 0xFFE7: ("APP7", "Application segment 7", APP),
+ 0xFFE8: ("APP8", "Application segment 8", APP),
+ 0xFFE9: ("APP9", "Application segment 9", APP),
+ 0xFFEA: ("APP10", "Application segment 10", APP),
+ 0xFFEB: ("APP11", "Application segment 11", APP),
+ 0xFFEC: ("APP12", "Application segment 12", APP),
+ 0xFFED: ("APP13", "Application segment 13", APP),
+ 0xFFEE: ("APP14", "Application segment 14", APP),
+ 0xFFEF: ("APP15", "Application segment 15", APP),
+ 0xFFF0: ("JPG0", "Extension 0", None),
+ 0xFFF1: ("JPG1", "Extension 1", None),
+ 0xFFF2: ("JPG2", "Extension 2", None),
+ 0xFFF3: ("JPG3", "Extension 3", None),
+ 0xFFF4: ("JPG4", "Extension 4", None),
+ 0xFFF5: ("JPG5", "Extension 5", None),
+ 0xFFF6: ("JPG6", "Extension 6", None),
+ 0xFFF7: ("JPG7", "Extension 7", None),
+ 0xFFF8: ("JPG8", "Extension 8", None),
+ 0xFFF9: ("JPG9", "Extension 9", None),
+ 0xFFFA: ("JPG10", "Extension 10", None),
+ 0xFFFB: ("JPG11", "Extension 11", None),
+ 0xFFFC: ("JPG12", "Extension 12", None),
+ 0xFFFD: ("JPG13", "Extension 13", None),
+ 0xFFFE: ("COM", "Comment", COM)
+}
+
+
+def _accept(prefix):
+ return prefix[0:1] == b"\377"
+
+
+##
+# Image plugin for JPEG and JFIF images.
+
+class JpegImageFile(ImageFile.ImageFile):
+
+ format = "JPEG"
+ format_description = "JPEG (ISO 10918)"
+
+ def _open(self):
+
+ s = self.fp.read(1)
+
+ if i8(s[0]) != 255:
+ raise SyntaxError("not a JPEG file")
+
+ # Create attributes
+ self.bits = self.layers = 0
+
+ # JPEG specifics (internal)
+ self.layer = []
+ self.huffman_dc = {}
+ self.huffman_ac = {}
+ self.quantization = {}
+ self.app = {} # compatibility
+ self.applist = []
+ self.icclist = []
+
+ while True:
+
+ i = i8(s)
+ if i == 0xFF:
+ s = s + self.fp.read(1)
+ i = i16(s)
+ else:
+ # Skip non-0xFF junk
+ s = b"\xff"
+ continue
+
+ if i in MARKER:
+ name, description, handler = MARKER[i]
+ # print hex(i), name, description
+ if handler is not None:
+ handler(self, i)
+ if i == 0xFFDA: # start of scan
+ rawmode = self.mode
+ if self.mode == "CMYK":
+ rawmode = "CMYK;I" # assume adobe conventions
+ self.tile = [("jpeg", (0, 0) + self.size, 0,
+ (rawmode, ""))]
+ # self.__offset = self.fp.tell()
+ break
+ s = self.fp.read(1)
+ elif i == 0 or i == 0xFFFF:
+ # padded marker or junk; move on
+ s = b"\xff"
+ else:
+ raise SyntaxError("no marker found")
+
+ def draft(self, mode, size):
+
+ if len(self.tile) != 1:
+ return
+
+ d, e, o, a = self.tile[0]
+ scale = 0
+
+ if a[0] == "RGB" and mode in ["L", "YCbCr"]:
+ self.mode = mode
+ a = mode, ""
+
+ if size:
+ scale = max(self.size[0] // size[0], self.size[1] // size[1])
+ for s in [8, 4, 2, 1]:
+ if scale >= s:
+ break
+ e = e[0], e[1], (e[2]-e[0]+s-1)//s+e[0], (e[3]-e[1]+s-1)//s+e[1]
+ self.size = ((self.size[0]+s-1)//s, (self.size[1]+s-1)//s)
+ scale = s
+
+ self.tile = [(d, e, o, a)]
+ self.decoderconfig = (scale, 0)
+
+ return self
+
+ def load_djpeg(self):
+
+ # ALTERNATIVE: handle JPEGs via the IJG command line utilities
+
+ import subprocess
+ import tempfile
+ import os
+ f, path = tempfile.mkstemp()
+ os.close(f)
+ if os.path.exists(self.filename):
+ subprocess.check_call(["djpeg", "-outfile", path, self.filename])
+ else:
+ raise ValueError("Invalid Filename")
+
+ try:
+ self.im = Image.core.open_ppm(path)
+ finally:
+ try:
+ os.unlink(path)
+ except:
+ pass
+
+ self.mode = self.im.mode
+ self.size = self.im.size
+
+ self.tile = []
+
+ def _getexif(self):
+ return _getexif(self)
+
+ def _getmp(self):
+ return _getmp(self)
+
+
+def _fixup(value):
+ # Helper function for _getexif() and _getmp()
+ if len(value) == 1:
+ return value[0]
+ return value
+
+
+def _getexif(self):
+ # Extract EXIF information. This method is highly experimental,
+ # and is likely to be replaced with something better in a future
+ # version.
+
+ # The EXIF record consists of a TIFF file embedded in a JPEG
+ # application marker (!).
+ try:
+ data = self.info["exif"]
+ except KeyError:
+ return None
+ file = io.BytesIO(data[6:])
+ head = file.read(8)
+ exif = {}
+ # process dictionary
+ info = TiffImagePlugin.ImageFileDirectory(head)
+ info.load(file)
+ for key, value in info.items():
+ exif[key] = _fixup(value)
+ # get exif extension
+ try:
+ file.seek(exif[0x8769])
+ except KeyError:
+ pass
+ else:
+ info = TiffImagePlugin.ImageFileDirectory(head)
+ info.load(file)
+ for key, value in info.items():
+ exif[key] = _fixup(value)
+ # get gpsinfo extension
+ try:
+ file.seek(exif[0x8825])
+ except KeyError:
+ pass
+ else:
+ info = TiffImagePlugin.ImageFileDirectory(head)
+ info.load(file)
+ exif[0x8825] = gps = {}
+ for key, value in info.items():
+ gps[key] = _fixup(value)
+ return exif
+
+
+def _getmp(self):
+ # Extract MP information. This method was inspired by the "highly
+ # experimental" _getexif version that's been in use for years now,
+ # itself based on the ImageFileDirectory class in the TIFF plug-in.
+
+ # The MP record essentially consists of a TIFF file embedded in a JPEG
+ # application marker.
+ try:
+ data = self.info["mp"]
+ except KeyError:
+ return None
+ file = io.BytesIO(data)
+ head = file.read(8)
+ endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
+ mp = {}
+ # process dictionary
+ info = TiffImagePlugin.ImageFileDirectory(head)
+ info.load(file)
+ for key, value in info.items():
+ mp[key] = _fixup(value)
+ # it's an error not to have a number of images
+ try:
+ quant = mp[0xB001]
+ except KeyError:
+ raise SyntaxError("malformed MP Index (no number of images)")
+ # get MP entries
+ try:
+ mpentries = []
+ for entrynum in range(0, quant):
+ rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
+ unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
+ labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
+ 'EntryNo2')
+ mpentry = dict(zip(labels, unpackedentry))
+ mpentryattr = {
+ 'DependentParentImageFlag': bool(mpentry['Attribute'] &
+ (1 << 31)),
+ 'DependentChildImageFlag': bool(mpentry['Attribute'] &
+ (1 << 30)),
+ 'RepresentativeImageFlag': bool(mpentry['Attribute'] &
+ (1 << 29)),
+ 'Reserved': (mpentry['Attribute'] & (3 << 27)) >> 27,
+ 'ImageDataFormat': (mpentry['Attribute'] & (7 << 24)) >> 24,
+ 'MPType': mpentry['Attribute'] & 0x00FFFFFF
+ }
+ if mpentryattr['ImageDataFormat'] == 0:
+ mpentryattr['ImageDataFormat'] = 'JPEG'
+ else:
+ raise SyntaxError("unsupported picture format in MPO")
+ mptypemap = {
+ 0x000000: 'Undefined',
+ 0x010001: 'Large Thumbnail (VGA Equivalent)',
+ 0x010002: 'Large Thumbnail (Full HD Equivalent)',
+ 0x020001: 'Multi-Frame Image (Panorama)',
+ 0x020002: 'Multi-Frame Image: (Disparity)',
+ 0x020003: 'Multi-Frame Image: (Multi-Angle)',
+ 0x030000: 'Baseline MP Primary Image'
+ }
+ mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
+ 'Unknown')
+ mpentry['Attribute'] = mpentryattr
+ mpentries.append(mpentry)
+ mp[0xB002] = mpentries
+ except KeyError:
+ raise SyntaxError("malformed MP Index (bad MP Entry)")
+ # Next we should try and parse the individual image unique ID list;
+ # we don't because I've never seen this actually used in a real MPO
+ # file and so can't test it.
+ return mp
+
+
+# --------------------------------------------------------------------
+# stuff to save JPEG files
+
+RAWMODE = {
+ "1": "L",
+ "L": "L",
+ "RGB": "RGB",
+ "RGBA": "RGB",
+ "RGBX": "RGB",
+ "CMYK": "CMYK;I", # assume adobe conventions
+ "YCbCr": "YCbCr",
+}
+
+zigzag_index = ( 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63)
+
+samplings = {(1, 1, 1, 1, 1, 1): 0,
+ (2, 1, 1, 1, 1, 1): 1,
+ (2, 2, 1, 1, 1, 1): 2,
+ }
+
+
+def convert_dict_qtables(qtables):
+ qtables = [qtables[key] for key in range(len(qtables)) if key in qtables]
+ for idx, table in enumerate(qtables):
+ qtables[idx] = [table[i] for i in zigzag_index]
+ return qtables
+
+
+def get_sampling(im):
+ # There's no subsampling when image have only 1 layer
+ # (grayscale images) or when they are CMYK (4 layers),
+ # so set subsampling to default value.
+ #
+ # NOTE: currently Pillow can't encode JPEG to YCCK format.
+ # If YCCK support is added in the future, subsampling code will have
+ # to be updated (here and in JpegEncode.c) to deal with 4 layers.
+ if not hasattr(im, 'layers') or im.layers in (1, 4):
+ return -1
+ sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
+ return samplings.get(sampling, -1)
+
+
+def _save(im, fp, filename):
+
+ try:
+ rawmode = RAWMODE[im.mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as JPEG" % im.mode)
+
+ info = im.encoderinfo
+
+ dpi = info.get("dpi", (0, 0))
+
+ quality = info.get("quality", 0)
+ subsampling = info.get("subsampling", -1)
+ qtables = info.get("qtables")
+
+ if quality == "keep":
+ quality = 0
+ subsampling = "keep"
+ qtables = "keep"
+ elif quality in presets:
+ preset = presets[quality]
+ quality = 0
+ subsampling = preset.get('subsampling', -1)
+ qtables = preset.get('quantization')
+ elif not isinstance(quality, int):
+ raise ValueError("Invalid quality setting")
+ else:
+ if subsampling in presets:
+ subsampling = presets[subsampling].get('subsampling', -1)
+ if isStringType(qtables) and qtables in presets:
+ qtables = presets[qtables].get('quantization')
+
+ if subsampling == "4:4:4":
+ subsampling = 0
+ elif subsampling == "4:2:2":
+ subsampling = 1
+ elif subsampling == "4:1:1":
+ subsampling = 2
+ elif subsampling == "keep":
+ if im.format != "JPEG":
+ raise ValueError(
+ "Cannot use 'keep' when original image is not a JPEG")
+ subsampling = get_sampling(im)
+
+ def validate_qtables(qtables):
+ if qtables is None:
+ return qtables
+ if isStringType(qtables):
+ try:
+ lines = [int(num) for line in qtables.splitlines()
+ for num in line.split('#', 1)[0].split()]
+ except ValueError:
+ raise ValueError("Invalid quantization table")
+ else:
+ qtables = [lines[s:s+64] for s in range(0, len(lines), 64)]
+ if isinstance(qtables, (tuple, list, dict)):
+ if isinstance(qtables, dict):
+ qtables = convert_dict_qtables(qtables)
+ elif isinstance(qtables, tuple):
+ qtables = list(qtables)
+ if not (0 < len(qtables) < 5):
+ raise ValueError("None or too many quantization tables")
+ for idx, table in enumerate(qtables):
+ try:
+ if len(table) != 64:
+ raise
+ table = array.array('b', table)
+ except TypeError:
+ raise ValueError("Invalid quantization table")
+ else:
+ qtables[idx] = list(table)
+ return qtables
+
+ if qtables == "keep":
+ if im.format != "JPEG":
+ raise ValueError(
+ "Cannot use 'keep' when original image is not a JPEG")
+ qtables = getattr(im, "quantization", None)
+ qtables = validate_qtables(qtables)
+
+ extra = b""
+
+ icc_profile = info.get("icc_profile")
+ if icc_profile:
+ ICC_OVERHEAD_LEN = 14
+ MAX_BYTES_IN_MARKER = 65533
+ MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
+ markers = []
+ while icc_profile:
+ markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])
+ icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
+ i = 1
+ for marker in markers:
+ size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker))
+ extra += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) +
+ o8(len(markers)) + marker)
+ i += 1
+
+ # get keyword arguments
+ im.encoderconfig = (
+ quality,
+ # "progressive" is the official name, but older documentation
+ # says "progression"
+ # FIXME: issue a warning if the wrong form is used (post-1.1.7)
+ "progressive" in info or "progression" in info,
+ info.get("smooth", 0),
+ "optimize" in info,
+ info.get("streamtype", 0),
+ dpi[0], dpi[1],
+ subsampling,
+ qtables,
+ extra,
+ info.get("exif", b"")
+ )
+
+ # if we optimize, libjpeg needs a buffer big enough to hold the whole image
+ # in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
+ # channels*size, this is a value that's been used in a django patch.
+ # https://github.com/jdriscoll/django-imagekit/issues/50
+ bufsize = 0
+ if "optimize" in info or "progressive" in info or "progression" in info:
+ # keep sets quality to 0, but the actual value may be high.
+ if quality >= 95 or quality == 0:
+ bufsize = 2 * im.size[0] * im.size[1]
+ else:
+ bufsize = im.size[0] * im.size[1]
+
+ # The exif info needs to be written as one block, + APP1, + one spare byte.
+ # Ensure that our buffer is big enough
+ bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
+
+ ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
+
+
+def _save_cjpeg(im, fp, filename):
+ # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
+ import os
+ import subprocess
+ tempfile = im._dump()
+ subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
+ try:
+ os.unlink(file)
+ except:
+ pass
+
+
+##
+# Factory for making JPEG and MPO instances
+def jpeg_factory(fp=None, filename=None):
+ im = JpegImageFile(fp, filename)
+ mpheader = im._getmp()
+ try:
+ if mpheader[45057] > 1:
+ # It's actually an MPO
+ from .MpoImagePlugin import MpoImageFile
+ im = MpoImageFile(fp, filename)
+ except (TypeError, IndexError):
+ # It is really a JPEG
+ pass
+ return im
+
+
+# -------------------------------------------------------------------q-
+# Registry stuff
+
+Image.register_open("JPEG", jpeg_factory, _accept)
+Image.register_save("JPEG", _save)
+
+Image.register_extension("JPEG", ".jfif")
+Image.register_extension("JPEG", ".jpe")
+Image.register_extension("JPEG", ".jpg")
+Image.register_extension("JPEG", ".jpeg")
+
+Image.register_mime("JPEG", "image/jpeg")
diff --git a/lib/Python/Lib/PIL/JpegPresets.py b/lib/Python/Lib/PIL/JpegPresets.py
new file mode 100644
index 000000000..6ca46d0cd
--- /dev/null
+++ b/lib/Python/Lib/PIL/JpegPresets.py
@@ -0,0 +1,241 @@
+"""
+JPEG quality settings equivalent to the Photoshop settings.
+
+More presets can be added to the presets dict if needed.
+
+Can be use when saving JPEG file.
+
+To apply the preset, specify::
+
+ quality="preset_name"
+
+To apply only the quantization table::
+
+ qtables="preset_name"
+
+To apply only the subsampling setting::
+
+ subsampling="preset_name"
+
+Example::
+
+ im.save("image_name.jpg", quality="web_high")
+
+
+Subsampling
+-----------
+
+Subsampling is the practice of encoding images by implementing less resolution
+for chroma information than for luma information.
+(ref.: http://en.wikipedia.org/wiki/Chroma_subsampling)
+
+Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and
+4:1:1 (or 4:2:0?).
+
+You can get the subsampling of a JPEG with the
+`JpegImagePlugin.get_subsampling(im)` function.
+
+
+Quantization tables
+-------------------
+
+They are values use by the DCT (Discrete cosine transform) to remove
+*unnecessary* information from the image (the lossy part of the compression).
+(ref.: http://en.wikipedia.org/wiki/Quantization_matrix#Quantization_matrices,
+http://en.wikipedia.org/wiki/JPEG#Quantization)
+
+You can get the quantization tables of a JPEG with::
+
+ im.quantization
+
+This will return a dict with a number of arrays. You can pass this dict
+directly as the qtables argument when saving a JPEG.
+
+The tables format between im.quantization and quantization in presets differ in
+3 ways:
+
+1. The base container of the preset is a list with sublists instead of dict.
+ dict[0] -> list[0], dict[1] -> list[1], ...
+2. Each table in a preset is a list instead of an array.
+3. The zigzag order is remove in the preset (needed by libjpeg >= 6a).
+
+You can convert the dict format to the preset format with the
+`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function.
+
+Libjpeg ref.: http://www.jpegcameras.com/libjpeg/libjpeg-3.html
+
+"""
+
+presets = {
+ 'web_low': {'subsampling': 2, # "4:1:1"
+ 'quantization': [
+ [20, 16, 25, 39, 50, 46, 62, 68,
+ 16, 18, 23, 38, 38, 53, 65, 68,
+ 25, 23, 31, 38, 53, 65, 68, 68,
+ 39, 38, 38, 53, 65, 68, 68, 68,
+ 50, 38, 53, 65, 68, 68, 68, 68,
+ 46, 53, 65, 68, 68, 68, 68, 68,
+ 62, 65, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68],
+ [21, 25, 32, 38, 54, 68, 68, 68,
+ 25, 28, 24, 38, 54, 68, 68, 68,
+ 32, 24, 32, 43, 66, 68, 68, 68,
+ 38, 38, 43, 53, 68, 68, 68, 68,
+ 54, 54, 66, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68]
+ ]},
+ 'web_medium': {'subsampling': 2, # "4:1:1"
+ 'quantization': [
+ [16, 11, 11, 16, 23, 27, 31, 30,
+ 11, 12, 12, 15, 20, 23, 23, 30,
+ 11, 12, 13, 16, 23, 26, 35, 47,
+ 16, 15, 16, 23, 26, 37, 47, 64,
+ 23, 20, 23, 26, 39, 51, 64, 64,
+ 27, 23, 26, 37, 51, 64, 64, 64,
+ 31, 23, 35, 47, 64, 64, 64, 64,
+ 30, 30, 47, 64, 64, 64, 64, 64],
+ [17, 15, 17, 21, 20, 26, 38, 48,
+ 15, 19, 18, 17, 20, 26, 35, 43,
+ 17, 18, 20, 22, 26, 30, 46, 53,
+ 21, 17, 22, 28, 30, 39, 53, 64,
+ 20, 20, 26, 30, 39, 48, 64, 64,
+ 26, 26, 30, 39, 48, 63, 64, 64,
+ 38, 35, 46, 53, 64, 64, 64, 64,
+ 48, 43, 53, 64, 64, 64, 64, 64]
+ ]},
+ 'web_high': {'subsampling': 0, # "4:4:4"
+ 'quantization': [
+ [ 6, 4, 4, 6, 9, 11, 12, 16,
+ 4, 5, 5, 6, 8, 10, 12, 12,
+ 4, 5, 5, 6, 10, 12, 14, 19,
+ 6, 6, 6, 11, 12, 15, 19, 28,
+ 9, 8, 10, 12, 16, 20, 27, 31,
+ 11, 10, 12, 15, 20, 27, 31, 31,
+ 12, 12, 14, 19, 27, 31, 31, 31,
+ 16, 12, 19, 28, 31, 31, 31, 31],
+ [ 7, 7, 13, 24, 26, 31, 31, 31,
+ 7, 12, 16, 21, 31, 31, 31, 31,
+ 13, 16, 17, 31, 31, 31, 31, 31,
+ 24, 21, 31, 31, 31, 31, 31, 31,
+ 26, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31]
+ ]},
+ 'web_very_high': {'subsampling': 0, # "4:4:4"
+ 'quantization': [
+ [ 2, 2, 2, 2, 3, 4, 5, 6,
+ 2, 2, 2, 2, 3, 4, 5, 6,
+ 2, 2, 2, 2, 4, 5, 7, 9,
+ 2, 2, 2, 4, 5, 7, 9, 12,
+ 3, 3, 4, 5, 8, 10, 12, 12,
+ 4, 4, 5, 7, 10, 12, 12, 12,
+ 5, 5, 7, 9, 12, 12, 12, 12,
+ 6, 6, 9, 12, 12, 12, 12, 12],
+ [ 3, 3, 5, 9, 13, 15, 15, 15,
+ 3, 4, 6, 11, 14, 12, 12, 12,
+ 5, 6, 9, 14, 12, 12, 12, 12,
+ 9, 11, 14, 12, 12, 12, 12, 12,
+ 13, 14, 12, 12, 12, 12, 12, 12,
+ 15, 12, 12, 12, 12, 12, 12, 12,
+ 15, 12, 12, 12, 12, 12, 12, 12,
+ 15, 12, 12, 12, 12, 12, 12, 12]
+ ]},
+ 'web_maximum': {'subsampling': 0, # "4:4:4"
+ 'quantization': [
+ [ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 2,
+ 1, 1, 1, 1, 1, 1, 2, 2,
+ 1, 1, 1, 1, 1, 2, 2, 3,
+ 1, 1, 1, 1, 2, 2, 3, 3,
+ 1, 1, 1, 2, 2, 3, 3, 3,
+ 1, 1, 2, 2, 3, 3, 3, 3],
+ [ 1, 1, 1, 2, 2, 3, 3, 3,
+ 1, 1, 1, 2, 3, 3, 3, 3,
+ 1, 1, 1, 3, 3, 3, 3, 3,
+ 2, 2, 3, 3, 3, 3, 3, 3,
+ 2, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3]
+ ]},
+ 'low': {'subsampling': 2, # "4:1:1"
+ 'quantization': [
+ [18, 14, 14, 21, 30, 35, 34, 17,
+ 14, 16, 16, 19, 26, 23, 12, 12,
+ 14, 16, 17, 21, 23, 12, 12, 12,
+ 21, 19, 21, 23, 12, 12, 12, 12,
+ 30, 26, 23, 12, 12, 12, 12, 12,
+ 35, 23, 12, 12, 12, 12, 12, 12,
+ 34, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12],
+ [20, 19, 22, 27, 20, 20, 17, 17,
+ 19, 25, 23, 14, 14, 12, 12, 12,
+ 22, 23, 14, 14, 12, 12, 12, 12,
+ 27, 14, 14, 12, 12, 12, 12, 12,
+ 20, 14, 12, 12, 12, 12, 12, 12,
+ 20, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12]
+ ]},
+ 'medium': {'subsampling': 2, # "4:1:1"
+ 'quantization': [
+ [12, 8, 8, 12, 17, 21, 24, 17,
+ 8, 9, 9, 11, 15, 19, 12, 12,
+ 8, 9, 10, 12, 19, 12, 12, 12,
+ 12, 11, 12, 21, 12, 12, 12, 12,
+ 17, 15, 19, 12, 12, 12, 12, 12,
+ 21, 19, 12, 12, 12, 12, 12, 12,
+ 24, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12],
+ [13, 11, 13, 16, 20, 20, 17, 17,
+ 11, 14, 14, 14, 14, 12, 12, 12,
+ 13, 14, 14, 14, 12, 12, 12, 12,
+ 16, 14, 14, 12, 12, 12, 12, 12,
+ 20, 14, 12, 12, 12, 12, 12, 12,
+ 20, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12]
+ ]},
+ 'high': {'subsampling': 0, # "4:4:4"
+ 'quantization': [
+ [ 6, 4, 4, 6, 9, 11, 12, 16,
+ 4, 5, 5, 6, 8, 10, 12, 12,
+ 4, 5, 5, 6, 10, 12, 12, 12,
+ 6, 6, 6, 11, 12, 12, 12, 12,
+ 9, 8, 10, 12, 12, 12, 12, 12,
+ 11, 10, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ 16, 12, 12, 12, 12, 12, 12, 12],
+ [ 7, 7, 13, 24, 20, 20, 17, 17,
+ 7, 12, 16, 14, 14, 12, 12, 12,
+ 13, 16, 14, 14, 12, 12, 12, 12,
+ 24, 14, 14, 12, 12, 12, 12, 12,
+ 20, 14, 12, 12, 12, 12, 12, 12,
+ 20, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12,
+ 17, 12, 12, 12, 12, 12, 12, 12]
+ ]},
+ 'maximum': {'subsampling': 0, # "4:4:4"
+ 'quantization': [
+ [ 2, 2, 2, 2, 3, 4, 5, 6,
+ 2, 2, 2, 2, 3, 4, 5, 6,
+ 2, 2, 2, 2, 4, 5, 7, 9,
+ 2, 2, 2, 4, 5, 7, 9, 12,
+ 3, 3, 4, 5, 8, 10, 12, 12,
+ 4, 4, 5, 7, 10, 12, 12, 12,
+ 5, 5, 7, 9, 12, 12, 12, 12,
+ 6, 6, 9, 12, 12, 12, 12, 12],
+ [ 3, 3, 5, 9, 13, 15, 15, 15,
+ 3, 4, 6, 10, 14, 12, 12, 12,
+ 5, 6, 9, 14, 12, 12, 12, 12,
+ 9, 10, 14, 12, 12, 12, 12, 12,
+ 13, 14, 12, 12, 12, 12, 12, 12,
+ 15, 12, 12, 12, 12, 12, 12, 12,
+ 15, 12, 12, 12, 12, 12, 12, 12,
+ 15, 12, 12, 12, 12, 12, 12, 12]
+ ]},
+}
diff --git a/lib/Python/Lib/PIL/McIdasImagePlugin.py b/lib/Python/Lib/PIL/McIdasImagePlugin.py
new file mode 100644
index 000000000..c3f255fd2
--- /dev/null
+++ b/lib/Python/Lib/PIL/McIdasImagePlugin.py
@@ -0,0 +1,74 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Basic McIdas support for PIL
+#
+# History:
+# 1997-05-05 fl Created (8-bit images only)
+# 2009-03-08 fl Added 16/32-bit support.
+#
+# Thanks to Richard Jones and Craig Swank for specs and samples.
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.2"
+
+import struct
+from PIL import Image, ImageFile
+
+
+def _accept(s):
+ return s[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04"
+
+
+##
+# Image plugin for McIdas area images.
+
+class McIdasImageFile(ImageFile.ImageFile):
+
+ format = "MCIDAS"
+ format_description = "McIdas area file"
+
+ def _open(self):
+
+ # parse area file directory
+ s = self.fp.read(256)
+ if not _accept(s) or len(s) != 256:
+ raise SyntaxError("not an McIdas area file")
+
+ self.area_descriptor_raw = s
+ self.area_descriptor = w = [0] + list(struct.unpack("!64i", s))
+
+ # get mode
+ if w[11] == 1:
+ mode = rawmode = "L"
+ elif w[11] == 2:
+ # FIXME: add memory map support
+ mode = "I"
+ rawmode = "I;16B"
+ elif w[11] == 4:
+ # FIXME: add memory map support
+ mode = "I"
+ rawmode = "I;32B"
+ else:
+ raise SyntaxError("unsupported McIdas format")
+
+ self.mode = mode
+ self.size = w[10], w[9]
+
+ offset = w[34] + w[15]
+ stride = w[15] + w[10]*w[11]*w[14]
+
+ self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
+
+# --------------------------------------------------------------------
+# registry
+
+Image.register_open("MCIDAS", McIdasImageFile, _accept)
+
+# no default extension
diff --git a/lib/Python/Lib/PIL/MicImagePlugin.py b/lib/Python/Lib/PIL/MicImagePlugin.py
new file mode 100644
index 000000000..cdfaf3eda
--- /dev/null
+++ b/lib/Python/Lib/PIL/MicImagePlugin.py
@@ -0,0 +1,96 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Microsoft Image Composer support for PIL
+#
+# Notes:
+# uses TiffImagePlugin.py to read the actual image streams
+#
+# History:
+# 97-01-20 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+
+from PIL import Image, TiffImagePlugin
+from PIL.OleFileIO import *
+
+
+#
+# --------------------------------------------------------------------
+
+
+def _accept(prefix):
+ return prefix[:8] == MAGIC
+
+
+##
+# Image plugin for Microsoft's Image Composer file format.
+
+class MicImageFile(TiffImagePlugin.TiffImageFile):
+
+ format = "MIC"
+ format_description = "Microsoft Image Composer"
+
+ def _open(self):
+
+ # read the OLE directory and see if this is a likely
+ # to be a Microsoft Image Composer file
+
+ try:
+ self.ole = OleFileIO(self.fp)
+ except IOError:
+ raise SyntaxError("not an MIC file; invalid OLE file")
+
+ # find ACI subfiles with Image members (maybe not the
+ # best way to identify MIC files, but what the... ;-)
+
+ self.images = []
+ for file in self.ole.listdir():
+ if file[1:] and file[0][-4:] == ".ACI" and file[1] == "Image":
+ self.images.append(file)
+
+ # if we didn't find any images, this is probably not
+ # an MIC file.
+ if not self.images:
+ raise SyntaxError("not an MIC file; no image entries")
+
+ self.__fp = self.fp
+ self.frame = 0
+
+ if len(self.images) > 1:
+ self.category = Image.CONTAINER
+
+ self.seek(0)
+
+ def seek(self, frame):
+
+ try:
+ filename = self.images[frame]
+ except IndexError:
+ raise EOFError("no such frame")
+
+ self.fp = self.ole.openstream(filename)
+
+ TiffImagePlugin.TiffImageFile._open(self)
+
+ self.frame = frame
+
+ def tell(self):
+
+ return self.frame
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("MIC", MicImageFile, _accept)
+
+Image.register_extension("MIC", ".mic")
diff --git a/lib/Python/Lib/PIL/MpegImagePlugin.py b/lib/Python/Lib/PIL/MpegImagePlugin.py
new file mode 100644
index 000000000..9aca58f16
--- /dev/null
+++ b/lib/Python/Lib/PIL/MpegImagePlugin.py
@@ -0,0 +1,85 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# MPEG file handling
+#
+# History:
+# 95-09-09 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1995.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.1"
+
+from PIL import Image, ImageFile
+from PIL._binary import i8
+
+
+#
+# Bitstream parser
+
+class BitStream:
+
+ def __init__(self, fp):
+ self.fp = fp
+ self.bits = 0
+ self.bitbuffer = 0
+
+ def next(self):
+ return i8(self.fp.read(1))
+
+ def peek(self, bits):
+ while self.bits < bits:
+ c = self.next()
+ if c < 0:
+ self.bits = 0
+ continue
+ self.bitbuffer = (self.bitbuffer << 8) + c
+ self.bits += 8
+ return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
+
+ def skip(self, bits):
+ while self.bits < bits:
+ self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1))
+ self.bits += 8
+ self.bits = self.bits - bits
+
+ def read(self, bits):
+ v = self.peek(bits)
+ self.bits = self.bits - bits
+ return v
+
+
+##
+# Image plugin for MPEG streams. This plugin can identify a stream,
+# but it cannot read it.
+
+class MpegImageFile(ImageFile.ImageFile):
+
+ format = "MPEG"
+ format_description = "MPEG"
+
+ def _open(self):
+
+ s = BitStream(self.fp)
+
+ if s.read(32) != 0x1B3:
+ raise SyntaxError("not an MPEG file")
+
+ self.mode = "RGB"
+ self.size = s.read(12), s.read(12)
+
+
+# --------------------------------------------------------------------
+# Registry stuff
+
+Image.register_open("MPEG", MpegImageFile)
+
+Image.register_extension("MPEG", ".mpg")
+Image.register_extension("MPEG", ".mpeg")
+
+Image.register_mime("MPEG", "video/mpeg")
diff --git a/lib/Python/Lib/PIL/MpoImagePlugin.py b/lib/Python/Lib/PIL/MpoImagePlugin.py
new file mode 100644
index 000000000..c345fd327
--- /dev/null
+++ b/lib/Python/Lib/PIL/MpoImagePlugin.py
@@ -0,0 +1,90 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# MPO file handling
+#
+# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the
+# Camera & Imaging Products Association)
+#
+# The multi-picture object combines multiple JPEG images (with a modified EXIF
+# data format) into a single file. While it can theoretically be used much like
+# a GIF animation, it is commonly used to represent 3D photographs and is (as
+# of this writing) the most commonly used format by 3D cameras.
+#
+# History:
+# 2014-03-13 Feneric Created
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.1"
+
+from PIL import Image, JpegImagePlugin
+
+
+def _accept(prefix):
+ return JpegImagePlugin._accept(prefix)
+
+
+def _save(im, fp, filename):
+ # Note that we can only save the current frame at present
+ return JpegImagePlugin._save(im, fp, filename)
+
+
+##
+# Image plugin for MPO images.
+
+class MpoImageFile(JpegImagePlugin.JpegImageFile):
+
+ format = "MPO"
+ format_description = "MPO (CIPA DC-007)"
+
+ def _open(self):
+ self.fp.seek(0) # prep the fp in order to pass the JPEG test
+ JpegImagePlugin.JpegImageFile._open(self)
+ self.mpinfo = self._getmp()
+ self.__framecount = self.mpinfo[0xB001]
+ self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset']
+ for mpent in self.mpinfo[0xB002]]
+ self.__mpoffsets[0] = 0
+ # Note that the following assertion will only be invalid if something
+ # gets broken within JpegImagePlugin.
+ assert self.__framecount == len(self.__mpoffsets)
+ del self.info['mpoffset'] # no longer needed
+ self.__fp = self.fp # FIXME: hack
+ self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
+ self.__frame = 0
+ self.offset = 0
+ # for now we can only handle reading and individual frame extraction
+ self.readonly = 1
+
+ def load_seek(self, pos):
+ self.__fp.seek(pos)
+
+ def seek(self, frame):
+ if frame < 0 or frame >= self.__framecount:
+ raise EOFError("no more images in MPO file")
+ else:
+ self.fp = self.__fp
+ self.offset = self.__mpoffsets[frame]
+ self.tile = [
+ ("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
+ ]
+ self.__frame = frame
+
+ def tell(self):
+ return self.__frame
+
+
+# -------------------------------------------------------------------q-
+# Registry stuff
+
+# Note that since MPO shares a factory with JPEG, we do not need to do a
+# separate registration for it here.
+# Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
+Image.register_save("MPO", _save)
+
+Image.register_extension("MPO", ".mpo")
+
+Image.register_mime("MPO", "image/mpo")
diff --git a/lib/Python/Lib/PIL/MspImagePlugin.py b/lib/Python/Lib/PIL/MspImagePlugin.py
new file mode 100644
index 000000000..4753be7cd
--- /dev/null
+++ b/lib/Python/Lib/PIL/MspImagePlugin.py
@@ -0,0 +1,104 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# MSP file handling
+#
+# This is the format used by the Paint program in Windows 1 and 2.
+#
+# History:
+# 95-09-05 fl Created
+# 97-01-03 fl Read/write MSP images
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1995-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+from PIL import Image, ImageFile, _binary
+
+
+#
+# read MSP files
+
+i16 = _binary.i16le
+
+
+def _accept(prefix):
+ return prefix[:4] in [b"DanM", b"LinS"]
+
+
+##
+# Image plugin for Windows MSP images. This plugin supports both
+# uncompressed (Windows 1.0).
+
+class MspImageFile(ImageFile.ImageFile):
+
+ format = "MSP"
+ format_description = "Windows Paint"
+
+ def _open(self):
+
+ # Header
+ s = self.fp.read(32)
+ if s[:4] not in [b"DanM", b"LinS"]:
+ raise SyntaxError("not an MSP file")
+
+ # Header checksum
+ sum = 0
+ for i in range(0, 32, 2):
+ sum = sum ^ i16(s[i:i+2])
+ if sum != 0:
+ raise SyntaxError("bad MSP checksum")
+
+ self.mode = "1"
+ self.size = i16(s[4:]), i16(s[6:])
+
+ if s[:4] == b"DanM":
+ self.tile = [("raw", (0, 0)+self.size, 32, ("1", 0, 1))]
+ else:
+ self.tile = [("msp", (0, 0)+self.size, 32+2*self.size[1], None)]
+
+#
+# write MSP files (uncompressed only)
+
+o16 = _binary.o16le
+
+
+def _save(im, fp, filename):
+
+ if im.mode != "1":
+ raise IOError("cannot write mode %s as MSP" % im.mode)
+
+ # create MSP header
+ header = [0] * 16
+
+ header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1
+ header[2], header[3] = im.size
+ header[4], header[5] = 1, 1
+ header[6], header[7] = 1, 1
+ header[8], header[9] = im.size
+
+ sum = 0
+ for h in header:
+ sum = sum ^ h
+ header[12] = sum # FIXME: is this the right field?
+
+ # header
+ for h in header:
+ fp.write(o16(h))
+
+ # image body
+ ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
+
+#
+# registry
+
+Image.register_open("MSP", MspImageFile, _accept)
+Image.register_save("MSP", _save)
+
+Image.register_extension("MSP", ".msp")
diff --git a/lib/Python/Lib/PIL/OleFileIO-README.md b/lib/Python/Lib/PIL/OleFileIO-README.md
new file mode 100644
index 000000000..99d8b0abb
--- /dev/null
+++ b/lib/Python/Lib/PIL/OleFileIO-README.md
@@ -0,0 +1,351 @@
+OleFileIO_PL
+============
+
+[OleFileIO_PL](http://www.decalage.info/python/olefileio) is a Python module to parse and read [Microsoft OLE2 files (also called Structured Storage, Compound File Binary Format or Compound Document File Format)](http://en.wikipedia.org/wiki/Compound_File_Binary_Format), such as Microsoft Office documents, Image Composer and FlashPix files, Outlook messages, StickyNotes, several Microscopy file formats ...
+
+This is an improved version of the OleFileIO module from [PIL](http://www.pythonware.com/products/pil/index.htm), the excellent Python Imaging Library, created and maintained by Fredrik Lundh. The API is still compatible with PIL, but since 2005 I have improved the internal implementation significantly, with new features, bugfixes and a more robust design.
+
+As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules)
+
+OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL.
+
+OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL.
+
+News
+----
+
+Follow all updates and news on Twitter: <https://twitter.com/decalage2>
+
+- **2014-02-04 v0.30**: now compatible with Python 3.x, thanks to Martin Panter who did most of the hard work.
+- 2013-07-24 v0.26: added methods to parse stream/storage timestamps, improved listdir to include storages, fixed parsing of direntry timestamps
+- 2013-05-27 v0.25: improved metadata extraction, properties parsing and exception handling, fixed [issue #12](https://bitbucket.org/decalage/olefileio_pl/issue/12/error-when-converting-timestamps-in-ole)
+- 2013-05-07 v0.24: new features to extract metadata (get\_metadata method and OleMetadata class), improved getproperties to convert timestamps to Python datetime
+- 2012-10-09: published [python-oletools](http://www.decalage.info/python/oletools), a package of analysis tools based on OleFileIO_PL
+- 2012-09-11 v0.23: added support for file-like objects, fixed [issue #8](https://bitbucket.org/decalage/olefileio_pl/issue/8/bug-with-file-object)
+- 2012-02-17 v0.22: fixed issues #7 (bug in getproperties) and #2 (added close method)
+- 2011-10-20: code hosted on bitbucket to ease contributions and bug tracking
+- 2010-01-24 v0.21: fixed support for big-endian CPUs, such as PowerPC Macs.
+- 2009-12-11 v0.20: small bugfix in OleFileIO.open when filename is not plain str.
+- 2009-12-10 v0.19: fixed support for 64 bits platforms (thanks to Ben G. and Martijn for reporting the bug)
+- see changelog in source code for more info.
+
+Download
+--------
+
+The archive is available on [the project page](https://bitbucket.org/decalage/olefileio_pl/downloads).
+
+Features
+--------
+
+- Parse and read any OLE file such as Microsoft Office 97-2003 legacy document formats (Word .doc, Excel .xls, PowerPoint .ppt, Visio .vsd, Project .mpp), Image Composer and FlashPix files, Outlook messages, StickyNotes, Zeiss AxioVision ZVI files, Olympus FluoView OIB files, ...
+- List all the streams and storages contained in an OLE file
+- Open streams as files
+- Parse and read property streams, containing metadata of the file
+- Portable, pure Python module, no dependency
+
+
+Main improvements over the original version of OleFileIO in PIL:
+----------------------------------------------------------------
+
+- Compatible with Python 3.x and 2.6+
+- Many bug fixes
+- Support for files larger than 6.8MB
+- Support for 64 bits platforms and big-endian CPUs
+- Robust: many checks to detect malformed files
+- Runtime option to choose if malformed files should be parsed or raise exceptions
+- Improved API
+- Metadata extraction, stream/storage timestamps (e.g. for document forensics)
+- Can open file-like objects
+- Added setup.py and install.bat to ease installation
+- More convenient slash-based syntax for stream paths
+
+
+
+How to use this module
+----------------------
+
+OleFileIO_PL can be used as an independent module or with PIL. The main functions and methods are explained below.
+
+For more information, see also the file **OleFileIO_PL.html**, sample code at the end of the module itself, and docstrings within the code.
+
+### About the structure of OLE files ###
+
+An OLE file can be seen as a mini file system or a Zip archive: It contains **streams** of data that look like files embedded within the OLE file. Each stream has a name. For example, the main stream of a MS Word document containing its text is named "WordDocument".
+
+An OLE file can also contain **storages**. A storage is a folder that contains streams or other storages. For example, a MS Word document with VBA macros has a storage called "Macros".
+
+Special streams can contain **properties**. A property is a specific value that can be used to store information such as the metadata of a document (title, author, creation date, etc). Property stream names usually start with the character '\x05'.
+
+For example, a typical MS Word document may look like this:
+
+ \x05DocumentSummaryInformation (stream)
+ \x05SummaryInformation (stream)
+ WordDocument (stream)
+ Macros (storage)
+ PROJECT (stream)
+ PROJECTwm (stream)
+ VBA (storage)
+ Module1 (stream)
+ ThisDocument (stream)
+ _VBA_PROJECT (stream)
+ dir (stream)
+ ObjectPool (storage)
+
+
+
+### Import OleFileIO_PL ###
+
+ :::python
+ import OleFileIO_PL
+
+As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. If your application needs to be compatible with Python 2.5 or older, you may use the following code to load the old version when needed:
+
+ :::python
+ try:
+ import OleFileIO_PL
+ except:
+ import OleFileIO_PL2 as OleFileIO_PL
+
+If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
+
+
+### Test if a file is an OLE container ###
+
+Use isOleFile to check if the first bytes of the file contain the Magic for OLE files, before opening it. isOleFile returns True if it is an OLE file, False otherwise (new in v0.16).
+
+ :::python
+ assert OleFileIO_PL.isOleFile('myfile.doc')
+
+
+### Open an OLE file from disk ###
+
+Create an OleFileIO object with the file path as parameter:
+
+ :::python
+ ole = OleFileIO_PL.OleFileIO('myfile.doc')
+
+### Open an OLE file from a file-like object ###
+
+This is useful if the file is not on disk, e.g. already stored in a string or as a file-like object.
+
+ :::python
+ ole = OleFileIO_PL.OleFileIO(f)
+
+For example the code below reads a file into a string, then uses BytesIO to turn it into a file-like object.
+
+ :::python
+ data = open('myfile.doc', 'rb').read()
+ f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x
+ ole = OleFileIO_PL.OleFileIO(f)
+
+### How to handle malformed OLE files ###
+
+By default, the parser is configured to be as robust and permissive as possible, allowing to parse most malformed OLE files. Only fatal errors will raise an exception. It is possible to tell the parser to be more strict in order to raise exceptions for files that do not fully conform to the OLE specifications, using the raise_defect option (new in v0.14):
+
+ :::python
+ ole = OleFileIO_PL.OleFileIO('myfile.doc', raise_defects=DEFECT_INCORRECT)
+
+When the parsing is done, the list of non-fatal issues detected is available as a list in the parsing_issues attribute of the OleFileIO object (new in 0.25):
+
+ :::python
+ print('Non-fatal issues raised during parsing:')
+ if ole.parsing_issues:
+ for exctype, msg in ole.parsing_issues:
+ print('- %s: %s' % (exctype.__name__, msg))
+ else:
+ print('None')
+
+
+### Syntax for stream and storage path ###
+
+Two different syntaxes are allowed for methods that need or return the path of streams and storages:
+
+1) Either a **list of strings** including all the storages from the root up to the stream/storage name. For example a stream called "WordDocument" at the root will have ['WordDocument'] as full path. A stream called "ThisDocument" located in the storage "Macros/VBA" will be ['Macros', 'VBA', 'ThisDocument']. This is the original syntax from PIL. While hard to read and not very convenient, this syntax works in all cases.
+
+2) Or a **single string with slashes** to separate storage and stream names (similar to the Unix path syntax). The previous examples would be 'WordDocument' and 'Macros/VBA/ThisDocument'. This syntax is easier, but may fail if a stream or storage name contains a slash. (new in v0.15)
+
+Both are case-insensitive.
+
+Switching between the two is easy:
+
+ :::python
+ slash_path = '/'.join(list_path)
+ list_path = slash_path.split('/')
+
+
+### Get the list of streams ###
+
+listdir() returns a list of all the streams contained in the OLE file, including those stored in storages. Each stream is listed itself as a list, as described above.
+
+ :::python
+ print(ole.listdir())
+
+Sample result:
+
+ :::python
+ [['\x01CompObj'], ['\x05DocumentSummaryInformation'], ['\x05SummaryInformation']
+ , ['1Table'], ['Macros', 'PROJECT'], ['Macros', 'PROJECTwm'], ['Macros', 'VBA',
+ 'Module1'], ['Macros', 'VBA', 'ThisDocument'], ['Macros', 'VBA', '_VBA_PROJECT']
+ , ['Macros', 'VBA', 'dir'], ['ObjectPool'], ['WordDocument']]
+
+As an option it is possible to choose if storages should also be listed, with or without streams (new in v0.26):
+
+ :::python
+ ole.listdir (streams=False, storages=True)
+
+
+### Test if known streams/storages exist: ###
+
+exists(path) checks if a given stream or storage exists in the OLE file (new in v0.16).
+
+ :::python
+ if ole.exists('worddocument'):
+ print("This is a Word document.")
+ if ole.exists('macros/vba'):
+ print("This document seems to contain VBA macros.")
+
+
+### Read data from a stream ###
+
+openstream(path) opens a stream as a file-like object.
+
+The following example extracts the "Pictures" stream from a PPT file:
+
+ :::python
+ pics = ole.openstream('Pictures')
+ data = pics.read()
+
+
+### Get information about a stream/storage ###
+
+Several methods can provide the size, type and timestamps of a given stream/storage:
+
+get_size(path) returns the size of a stream in bytes (new in v0.16):
+
+ :::python
+ s = ole.get_size('WordDocument')
+
+get_type(path) returns the type of a stream/storage, as one of the following constants: STGTY\_STREAM for a stream, STGTY\_STORAGE for a storage, STGTY\_ROOT for the root entry, and False for a non existing path (new in v0.15).
+
+ :::python
+ t = ole.get_type('WordDocument')
+
+get\_ctime(path) and get\_mtime(path) return the creation and modification timestamps of a stream/storage, as a Python datetime object with UTC timezone. Please note that these timestamps are only present if the application that created the OLE file explicitly stored them, which is rarely the case. When not present, these methods return None (new in v0.26).
+
+ :::python
+ c = ole.get_ctime('WordDocument')
+ m = ole.get_mtime('WordDocument')
+
+The root storage is a special case: You can get its creation and modification timestamps using the OleFileIO.root attribute (new in v0.26):
+
+ :::python
+ c = ole.root.getctime()
+ m = ole.root.getmtime()
+
+### Extract metadata ###
+
+get_metadata() will check if standard property streams exist, parse all the properties they contain, and return an OleMetadata object with the found properties as attributes (new in v0.24).
+
+ :::python
+ meta = ole.get_metadata()
+ print('Author:', meta.author)
+ print('Title:', meta.title)
+ print('Creation date:', meta.create_time)
+ # print all metadata:
+ meta.dump()
+
+Available attributes include:
+
+ codepage, title, subject, author, keywords, comments, template,
+ last_saved_by, revision_number, total_edit_time, last_printed, create_time,
+ last_saved_time, num_pages, num_words, num_chars, thumbnail,
+ creating_application, security, codepage_doc, category, presentation_target,
+ bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips,
+ scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty,
+ chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed,
+ version, dig_sig, content_type, content_status, language, doc_version
+
+See the source code of the OleMetadata class for more information.
+
+
+### Parse a property stream ###
+
+get\_properties(path) can be used to parse any property stream that is not handled by get\_metadata. It returns a dictionary indexed by integers. Each integer is the index of the property, pointing to its value. For example in the standard property stream '\x05SummaryInformation', the document title is property #2, and the subject is #3.
+
+ :::python
+ p = ole.getproperties('specialprops')
+
+By default as in the original PIL version, timestamp properties are converted into a number of seconds since Jan 1,1601. With the option convert\_time, you can obtain more convenient Python datetime objects (UTC timezone). If some time properties should not be converted (such as total editing time in '\x05SummaryInformation'), the list of indexes can be passed as no_conversion (new in v0.25):
+
+ :::python
+ p = ole.getproperties('specialprops', convert_time=True, no_conversion=[10])
+
+
+### Close the OLE file ###
+
+Unless your application is a simple script that terminates after processing an OLE file, do not forget to close each OleFileIO object after parsing to close the file on disk. (new in v0.22)
+
+ :::python
+ ole.close()
+
+### Use OleFileIO_PL as a script ###
+
+OleFileIO_PL can also be used as a script from the command-line to display the structure of an OLE file and its metadata, for example:
+
+ OleFileIO_PL.py myfile.doc
+
+You can use the option -c to check that all streams can be read fully, and -d to generate very verbose debugging information.
+
+## Real-life examples ##
+
+A real-life example: [using OleFileIO_PL for malware analysis and forensics](http://blog.gregback.net/2011/03/using-remnux-for-forensic-puzzle-6/).
+
+See also [this paper](https://computer-forensics.sans.org/community/papers/gcfa/grow-forensic-tools-taxonomy-python-libraries-helpful-forensic-analysis_6879) about python tools for forensics, which features OleFileIO_PL.
+
+About Python 2 and 3
+--------------------
+
+OleFileIO\_PL used to support only Python 2.x. As of version 0.30, the code has been changed to be compatible with Python 3.x. As a consequence, compatibility with Python 2.5 or older is not provided anymore. However, a copy of v0.26 is available as OleFileIO_PL2.py. See above the "import" section for a workaround.
+
+If you think OleFileIO_PL should stay compatible with Python 2.5 or older, please [contact me](http://decalage.info/contact).
+
+How to contribute
+-----------------
+
+The code is available in [a Mercurial repository on bitbucket](https://bitbucket.org/decalage/olefileio_pl). You may use it to submit enhancements or to report any issue.
+
+If you would like to help us improve this module, or simply provide feedback, please [contact me](http://decalage.info/contact). You can help in many ways:
+
+- test this module on different platforms / Python versions
+- find and report bugs
+- improve documentation, code samples, docstrings
+- write unittest test cases
+- provide tricky malformed files
+
+How to report bugs
+------------------
+
+To report a bug, for example a normal file which is not parsed correctly, please use the [issue reporting page](https://bitbucket.org/decalage/olefileio_pl/issues?status=new&status=open), or if you prefer to do it privately, use this [contact form](http://decalage.info/contact). Please provide all the information about the context and how to reproduce the bug.
+
+If possible please join the debugging output of OleFileIO_PL. For this, launch the following command :
+
+ OleFileIO_PL.py -d -c file >debug.txt
+
+License
+-------
+
+OleFileIO_PL is open-source.
+
+OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec.
+
+The Python Imaging Library (PIL) is
+
+- Copyright (c) 1997-2005 by Secret Labs AB
+
+- Copyright (c) 1995-2005 by Fredrik Lundh
+
+By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions:
+
+Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission.
+
+SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/lib/Python/Lib/PIL/OleFileIO.py b/lib/Python/Lib/PIL/OleFileIO.py
new file mode 100644
index 000000000..a08ae0ee3
--- /dev/null
+++ b/lib/Python/Lib/PIL/OleFileIO.py
@@ -0,0 +1,2087 @@
+#!/usr/bin/env python
+## OleFileIO_PL:
+## Module to read Microsoft OLE2 files (also called Structured Storage or
+## Microsoft Compound Document File Format), such as Microsoft Office
+## documents, Image Composer and FlashPix files, Outlook messages, ...
+## This version is compatible with Python 2.6+ and 3.x
+
+## version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info
+
+## Project website: http://www.decalage.info/python/olefileio
+
+## Improved version of the OleFileIO module from PIL library v1.1.6
+## See: http://www.pythonware.com/products/pil/index.htm
+
+## The Python Imaging Library (PIL) is
+
+## Copyright (c) 1997-2005 by Secret Labs AB
+## Copyright (c) 1995-2005 by Fredrik Lundh
+
+## OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
+
+## See source code and LICENSE.txt for information on usage and redistribution.
+
+## WARNING: THIS IS (STILL) WORK IN PROGRESS.
+
+
+# Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported
+# This import enables print() as a function rather than a keyword
+# (main requirement to be compatible with Python 3.x)
+# The comment on the line below should be printed on Python 2.5 or older:
+from __future__ import print_function # This version of OleFileIO_PL requires Python 2.6+ or 3.x.
+
+
+__author__ = "Philippe Lagadec, Fredrik Lundh (Secret Labs AB)"
+__date__ = "2014-02-04"
+__version__ = '0.30'
+
+#--- LICENSE ------------------------------------------------------------------
+
+# OleFileIO_PL is an improved version of the OleFileIO module from the
+# Python Imaging Library (PIL).
+
+# OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec
+#
+# The Python Imaging Library (PIL) is
+# Copyright (c) 1997-2005 by Secret Labs AB
+# Copyright (c) 1995-2005 by Fredrik Lundh
+#
+# By obtaining, using, and/or copying this software and/or its associated
+# documentation, you agree that you have read, understood, and will comply with
+# the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and its
+# associated documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appears in all copies, and that both
+# that copyright notice and this permission notice appear in supporting
+# documentation, and that the name of Secret Labs AB or the author(s) not be used
+# in advertising or publicity pertaining to distribution of the software
+# without specific, written prior permission.
+#
+# SECRET LABS AB AND THE AUTHORS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+# IN NO EVENT SHALL SECRET LABS AB OR THE AUTHORS BE LIABLE FOR ANY SPECIAL,
+# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+#-----------------------------------------------------------------------------
+# CHANGELOG: (only OleFileIO_PL changes compared to PIL 1.1.6)
+# 2005-05-11 v0.10 PL: - a few fixes for Python 2.4 compatibility
+# (all changes flagged with [PL])
+# 2006-02-22 v0.11 PL: - a few fixes for some Office 2003 documents which raise
+# exceptions in _OleStream.__init__()
+# 2006-06-09 v0.12 PL: - fixes for files above 6.8MB (DIFAT in loadfat)
+# - added some constants
+# - added header values checks
+# - added some docstrings
+# - getsect: bugfix in case sectors >512 bytes
+# - getsect: added conformity checks
+# - DEBUG_MODE constant to activate debug display
+# 2007-09-04 v0.13 PL: - improved/translated (lots of) comments
+# - updated license
+# - converted tabs to 4 spaces
+# 2007-11-19 v0.14 PL: - added OleFileIO._raise_defect() to adapt sensitivity
+# - improved _unicode() to use Python 2.x unicode support
+# - fixed bug in _OleDirectoryEntry
+# 2007-11-25 v0.15 PL: - added safety checks to detect FAT loops
+# - fixed _OleStream which didn't check stream size
+# - added/improved many docstrings and comments
+# - moved helper functions _unicode and _clsid out of
+# OleFileIO class
+# - improved OleFileIO._find() to add Unix path syntax
+# - OleFileIO._find() is now case-insensitive
+# - added get_type() and get_rootentry_name()
+# - rewritten loaddirectory and _OleDirectoryEntry
+# 2007-11-27 v0.16 PL: - added _OleDirectoryEntry.kids_dict
+# - added detection of duplicate filenames in storages
+# - added detection of duplicate references to streams
+# - added get_size() and exists() to _OleDirectoryEntry
+# - added isOleFile to check header before parsing
+# - added __all__ list to control public keywords in pydoc
+# 2007-12-04 v0.17 PL: - added _load_direntry to fix a bug in loaddirectory
+# - improved _unicode(), added workarounds for Python <2.3
+# - added set_debug_mode and -d option to set debug mode
+# - fixed bugs in OleFileIO.open and _OleDirectoryEntry
+# - added safety check in main for large or binary
+# properties
+# - allow size>0 for storages for some implementations
+# 2007-12-05 v0.18 PL: - fixed several bugs in handling of FAT, MiniFAT and
+# streams
+# - added option '-c' in main to check all streams
+# 2009-12-10 v0.19 PL: - bugfix for 32 bit arrays on 64 bits platforms
+# (thanks to Ben G. and Martijn for reporting the bug)
+# 2009-12-11 v0.20 PL: - bugfix in OleFileIO.open when filename is not plain str
+# 2010-01-22 v0.21 PL: - added support for big-endian CPUs such as PowerPC Macs
+# 2012-02-16 v0.22 PL: - fixed bug in getproperties, patch by chuckleberryfinn
+# (https://bitbucket.org/decalage/olefileio_pl/issue/7)
+# - added close method to OleFileIO (fixed issue #2)
+# 2012-07-25 v0.23 PL: - added support for file-like objects (patch by mete0r_kr)
+# 2013-05-05 v0.24 PL: - getproperties: added conversion from filetime to python
+# datetime
+# - main: displays properties with date format
+# - new class OleMetadata to parse standard properties
+# - added get_metadata method
+# 2013-05-07 v0.24 PL: - a few improvements in OleMetadata
+# 2013-05-24 v0.25 PL: - getproperties: option to not convert some timestamps
+# - OleMetaData: total_edit_time is now a number of seconds,
+# not a timestamp
+# - getproperties: added support for VT_BOOL, VT_INT, V_UINT
+# - getproperties: filter out null chars from strings
+# - getproperties: raise non-fatal defects instead of
+# exceptions when properties cannot be parsed properly
+# 2013-05-27 PL: - getproperties: improved exception handling
+# - _raise_defect: added option to set exception type
+# - all non-fatal issues are now recorded, and displayed
+# when run as a script
+# 2013-07-11 v0.26 PL: - added methods to get modification and creation times
+# of a directory entry or a storage/stream
+# - fixed parsing of direntry timestamps
+# 2013-07-24 PL: - new options in listdir to list storages and/or streams
+# 2014-02-04 v0.30 PL: - upgraded code to support Python 3.x by Martin Panter
+# - several fixes for Python 2.6 (xrange, MAGIC)
+# - reused i32 from Pillow's _binary
+
+#-----------------------------------------------------------------------------
+# TODO (for version 1.0):
+# + isOleFile should accept file-like objects like open
+# + fix how all the methods handle unicode str and/or bytes as arguments
+# + add path attrib to _OleDirEntry, set it once and for all in init or
+# append_kids (then listdir/_list can be simplified)
+# - TESTS with Linux, MacOSX, Python 1.5.2, various files, PIL, ...
+# - add underscore to each private method, to avoid their display in
+# pydoc/epydoc documentation - Remove it for classes to be documented
+# - replace all raised exceptions with _raise_defect (at least in OleFileIO)
+# - merge code from _OleStream and OleFileIO.getsect to read sectors
+# (maybe add a class for FAT and MiniFAT ?)
+# - add method to check all streams (follow sectors chains without storing all
+# stream in memory, and report anomalies)
+# - use _OleDirectoryEntry.kids_dict to improve _find and _list ?
+# - fix Unicode names handling (find some way to stay compatible with Py1.5.2)
+# => if possible avoid converting names to Latin-1
+# - review DIFAT code: fix handling of DIFSECT blocks in FAT (not stop)
+# - rewrite OleFileIO.getproperties
+# - improve docstrings to show more sample uses
+# - see also original notes and FIXME below
+# - remove all obsolete FIXMEs
+# - OleMetadata: fix version attrib according to
+# http://msdn.microsoft.com/en-us/library/dd945671%28v=office.12%29.aspx
+
+# IDEAS:
+# - in OleFileIO._open and _OleStream, use size=None instead of 0x7FFFFFFF for
+# streams with unknown size
+# - use arrays of int instead of long integers for FAT/MiniFAT, to improve
+# performance and reduce memory usage ? (possible issue with values >2^31)
+# - provide tests with unittest (may need write support to create samples)
+# - move all debug code (and maybe dump methods) to a separate module, with
+# a class which inherits OleFileIO ?
+# - fix docstrings to follow epydoc format
+# - add support for 4K sectors ?
+# - add support for big endian byte order ?
+# - create a simple OLE explorer with wxPython
+
+# FUTURE EVOLUTIONS to add write support:
+# 1) add ability to write a stream back on disk from BytesIO (same size, no
+# change in FAT/MiniFAT).
+# 2) rename a stream/storage if it doesn't change the RB tree
+# 3) use rbtree module to update the red-black tree + any rename
+# 4) remove a stream/storage: free sectors in FAT/MiniFAT
+# 5) allocate new sectors in FAT/MiniFAT
+# 6) create new storage/stream
+#-----------------------------------------------------------------------------
+
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library
+# $Id$
+#
+# stuff to deal with OLE2 Structured Storage files. this module is
+# used by PIL to read Image Composer and FlashPix files, but can also
+# be used to read other files of this type.
+#
+# History:
+# 1997-01-20 fl Created
+# 1997-01-22 fl Fixed 64-bit portability quirk
+# 2003-09-09 fl Fixed typo in OleFileIO.loadfat (noted by Daniel Haertle)
+# 2004-02-29 fl Changed long hex constants to signed integers
+#
+# Notes:
+# FIXME: sort out sign problem (eliminate long hex constants)
+# FIXME: change filename to use "a/b/c" instead of ["a", "b", "c"]
+# FIXME: provide a glob mechanism function (using fnmatchcase)
+#
+# Literature:
+#
+# "FlashPix Format Specification, Appendix A", Kodak and Microsoft,
+# September 1996.
+#
+# Quotes:
+#
+# "If this document and functionality of the Software conflict,
+# the actual functionality of the Software represents the correct
+# functionality" -- Microsoft, in the OLE format specification
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+#------------------------------------------------------------------------------
+
+
+import io
+import sys
+import struct, array, os.path, datetime
+
+#[PL] Define explicitly the public API to avoid private objects in pydoc:
+__all__ = ['OleFileIO', 'isOleFile', 'MAGIC']
+
+# For Python 3.x, need to redefine long as int:
+if str is not bytes:
+ long = int
+
+# Need to make sure we use xrange both on Python 2 and 3.x:
+try:
+ # on Python 2 we need xrange:
+ iterrange = xrange
+except:
+ # no xrange, for Python 3 it was renamed as range:
+ iterrange = range
+
+#[PL] workaround to fix an issue with array item size on 64 bits systems:
+if array.array('L').itemsize == 4:
+ # on 32 bits platforms, long integers in an array are 32 bits:
+ UINT32 = 'L'
+elif array.array('I').itemsize == 4:
+ # on 64 bits platforms, integers in an array are 32 bits:
+ UINT32 = 'I'
+else:
+ raise ValueError('Need to fix a bug with 32 bit arrays, please contact author...')
+
+
+#[PL] These workarounds were inspired from the Path module
+# (see http://www.jorendorff.com/articles/python/path/)
+#TODO: test with old Python versions
+
+# Pre-2.3 workaround for basestring.
+try:
+ basestring
+except NameError:
+ try:
+ # is Unicode supported (Python >2.0 or >1.6 ?)
+ basestring = (str, unicode)
+ except NameError:
+ basestring = str
+
+#[PL] Experimental setting: if True, OLE filenames will be kept in Unicode
+# if False (default PIL behaviour), all filenames are converted to Latin-1.
+KEEP_UNICODE_NAMES = False
+
+#[PL] DEBUG display mode: False by default, use set_debug_mode() or "-d" on
+# command line to change it.
+DEBUG_MODE = False
+def debug_print(msg):
+ print(msg)
+def debug_pass(msg):
+ pass
+debug = debug_pass
+
+def set_debug_mode(debug_mode):
+ """
+ Set debug mode on or off, to control display of debugging messages.
+ mode: True or False
+ """
+ global DEBUG_MODE, debug
+ DEBUG_MODE = debug_mode
+ if debug_mode:
+ debug = debug_print
+ else:
+ debug = debug_pass
+
+MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'
+
+#[PL]: added constants for Sector IDs (from AAF specifications)
+MAXREGSECT = 0xFFFFFFFA; # maximum SECT
+DIFSECT = 0xFFFFFFFC; # (-4) denotes a DIFAT sector in a FAT
+FATSECT = 0xFFFFFFFD; # (-3) denotes a FAT sector in a FAT
+ENDOFCHAIN = 0xFFFFFFFE; # (-2) end of a virtual stream chain
+FREESECT = 0xFFFFFFFF; # (-1) unallocated sector
+
+#[PL]: added constants for Directory Entry IDs (from AAF specifications)
+MAXREGSID = 0xFFFFFFFA; # maximum directory entry ID
+NOSTREAM = 0xFFFFFFFF; # (-1) unallocated directory entry
+
+#[PL] object types in storage (from AAF specifications)
+STGTY_EMPTY = 0 # empty directory entry (according to OpenOffice.org doc)
+STGTY_STORAGE = 1 # element is a storage object
+STGTY_STREAM = 2 # element is a stream object
+STGTY_LOCKBYTES = 3 # element is an ILockBytes object
+STGTY_PROPERTY = 4 # element is an IPropertyStorage object
+STGTY_ROOT = 5 # element is a root storage
+
+
+#
+# --------------------------------------------------------------------
+# property types
+
+VT_EMPTY=0; VT_NULL=1; VT_I2=2; VT_I4=3; VT_R4=4; VT_R8=5; VT_CY=6;
+VT_DATE=7; VT_BSTR=8; VT_DISPATCH=9; VT_ERROR=10; VT_BOOL=11;
+VT_VARIANT=12; VT_UNKNOWN=13; VT_DECIMAL=14; VT_I1=16; VT_UI1=17;
+VT_UI2=18; VT_UI4=19; VT_I8=20; VT_UI8=21; VT_INT=22; VT_UINT=23;
+VT_VOID=24; VT_HRESULT=25; VT_PTR=26; VT_SAFEARRAY=27; VT_CARRAY=28;
+VT_USERDEFINED=29; VT_LPSTR=30; VT_LPWSTR=31; VT_FILETIME=64;
+VT_BLOB=65; VT_STREAM=66; VT_STORAGE=67; VT_STREAMED_OBJECT=68;
+VT_STORED_OBJECT=69; VT_BLOB_OBJECT=70; VT_CF=71; VT_CLSID=72;
+VT_VECTOR=0x1000;
+
+# map property id to name (for debugging purposes)
+
+VT = {}
+for keyword, var in list(vars().items()):
+ if keyword[:3] == "VT_":
+ VT[var] = keyword
+
+#
+# --------------------------------------------------------------------
+# Some common document types (root.clsid fields)
+
+WORD_CLSID = "00020900-0000-0000-C000-000000000046"
+#TODO: check Excel, PPT, ...
+
+#[PL]: Defect levels to classify parsing errors - see OleFileIO._raise_defect()
+DEFECT_UNSURE = 10 # a case which looks weird, but not sure it's a defect
+DEFECT_POTENTIAL = 20 # a potential defect
+DEFECT_INCORRECT = 30 # an error according to specifications, but parsing
+ # can go on
+DEFECT_FATAL = 40 # an error which cannot be ignored, parsing is
+ # impossible
+
+#[PL] add useful constants to __all__:
+for key in list(vars().keys()):
+ if key.startswith('STGTY_') or key.startswith('DEFECT_'):
+ __all__.append(key)
+
+
+#--- FUNCTIONS ----------------------------------------------------------------
+
+def isOleFile (filename):
+ """
+ Test if file is an OLE container (according to its header).
+
+ :param filename: file name or path (str, unicode)
+ :returns: True if OLE, False otherwise.
+ """
+ f = open(filename, 'rb')
+ header = f.read(len(MAGIC))
+ if header == MAGIC:
+ return True
+ else:
+ return False
+
+
+if bytes is str:
+ # version for Python 2.x
+ def i8(c):
+ return ord(c)
+else:
+ # version for Python 3.x
+ def i8(c):
+ return c if c.__class__ is int else c[0]
+
+
+#TODO: replace i16 and i32 with more readable struct.unpack equivalent?
+
+def i16(c, o = 0):
+ """
+ Converts a 2-bytes (16 bits) string to an integer.
+
+ :param c: string containing bytes to convert
+ :param o: offset of bytes to convert in string
+ """
+ return i8(c[o]) | (i8(c[o+1])<<8)
+
+
+def i32(c, o = 0):
+ """
+ Converts a 4-bytes (32 bits) string to an integer.
+
+ :param c: string containing bytes to convert
+ :param o: offset of bytes to convert in string
+ """
+## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24))
+## # [PL]: added int() because "<<" gives long int since Python 2.4
+ # copied from Pillow's _binary:
+ return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24)
+
+
+def _clsid(clsid):
+ """
+ Converts a CLSID to a human-readable string.
+
+ :param clsid: string of length 16.
+ """
+ assert len(clsid) == 16
+ # if clsid is only made of null bytes, return an empty string:
+ # (PL: why not simply return the string with zeroes?)
+ if not clsid.strip(b"\0"):
+ return ""
+ return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) %
+ ((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) +
+ tuple(map(i8, clsid[8:16]))))
+
+
+
+# UNICODE support:
+# (necessary to handle storages/streams names which use Unicode)
+
+def _unicode(s, errors='replace'):
+ """
+ Map unicode string to Latin 1. (Python with Unicode support)
+
+ :param s: UTF-16LE unicode string to convert to Latin-1
+ :param errors: 'replace', 'ignore' or 'strict'.
+ """
+ #TODO: test if it OleFileIO works with Unicode strings, instead of
+ # converting to Latin-1.
+ try:
+ # First the string is converted to plain Unicode:
+ # (assuming it is encoded as UTF-16 little-endian)
+ u = s.decode('UTF-16LE', errors)
+ if bytes is not str or KEEP_UNICODE_NAMES:
+ return u
+ else:
+ # Second the unicode string is converted to Latin-1
+ return u.encode('latin_1', errors)
+ except:
+ # there was an error during Unicode to Latin-1 conversion:
+ raise IOError('incorrect Unicode name')
+
+
+def filetime2datetime(filetime):
+ """
+ convert FILETIME (64 bits int) to Python datetime.datetime
+ """
+ # TODO: manage exception when microseconds is too large
+ # inspired from http://code.activestate.com/recipes/511425-filetime-to-datetime/
+ _FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0)
+ #debug('timedelta days=%d' % (filetime//(10*1000000*3600*24)))
+ return _FILETIME_null_date + datetime.timedelta(microseconds=filetime//10)
+
+
+
+#=== CLASSES ==================================================================
+
+class OleMetadata:
+ """
+ class to parse and store metadata from standard properties of OLE files.
+
+ Available attributes:
+ codepage, title, subject, author, keywords, comments, template,
+ last_saved_by, revision_number, total_edit_time, last_printed, create_time,
+ last_saved_time, num_pages, num_words, num_chars, thumbnail,
+ creating_application, security, codepage_doc, category, presentation_target,
+ bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips,
+ scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty,
+ chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed,
+ version, dig_sig, content_type, content_status, language, doc_version
+
+ Note: an attribute is set to None when not present in the properties of the
+ OLE file.
+
+ References for SummaryInformation stream:
+ - http://msdn.microsoft.com/en-us/library/dd942545.aspx
+ - http://msdn.microsoft.com/en-us/library/dd925819%28v=office.12%29.aspx
+ - http://msdn.microsoft.com/en-us/library/windows/desktop/aa380376%28v=vs.85%29.aspx
+ - http://msdn.microsoft.com/en-us/library/aa372045.aspx
+ - http://sedna-soft.de/summary-information-stream/
+ - http://poi.apache.org/apidocs/org/apache/poi/hpsf/SummaryInformation.html
+
+ References for DocumentSummaryInformation stream:
+ - http://msdn.microsoft.com/en-us/library/dd945671%28v=office.12%29.aspx
+ - http://msdn.microsoft.com/en-us/library/windows/desktop/aa380374%28v=vs.85%29.aspx
+ - http://poi.apache.org/apidocs/org/apache/poi/hpsf/DocumentSummaryInformation.html
+
+ new in version 0.25
+ """
+
+ # attribute names for SummaryInformation stream properties:
+ # (ordered by property id, starting at 1)
+ SUMMARY_ATTRIBS = ['codepage', 'title', 'subject', 'author', 'keywords', 'comments',
+ 'template', 'last_saved_by', 'revision_number', 'total_edit_time',
+ 'last_printed', 'create_time', 'last_saved_time', 'num_pages',
+ 'num_words', 'num_chars', 'thumbnail', 'creating_application',
+ 'security']
+
+ # attribute names for DocumentSummaryInformation stream properties:
+ # (ordered by property id, starting at 1)
+ DOCSUM_ATTRIBS = ['codepage_doc', 'category', 'presentation_target', 'bytes', 'lines', 'paragraphs',
+ 'slides', 'notes', 'hidden_slides', 'mm_clips',
+ 'scale_crop', 'heading_pairs', 'titles_of_parts', 'manager',
+ 'company', 'links_dirty', 'chars_with_spaces', 'unused', 'shared_doc',
+ 'link_base', 'hlinks', 'hlinks_changed', 'version', 'dig_sig',
+ 'content_type', 'content_status', 'language', 'doc_version']
+
+ def __init__(self):
+ """
+ Constructor for OleMetadata
+ All attributes are set to None by default
+ """
+ # properties from SummaryInformation stream
+ self.codepage = None
+ self.title = None
+ self.subject = None
+ self.author = None
+ self.keywords = None
+ self.comments = None
+ self.template = None
+ self.last_saved_by = None
+ self.revision_number = None
+ self.total_edit_time = None
+ self.last_printed = None
+ self.create_time = None
+ self.last_saved_time = None
+ self.num_pages = None
+ self.num_words = None
+ self.num_chars = None
+ self.thumbnail = None
+ self.creating_application = None
+ self.security = None
+ # properties from DocumentSummaryInformation stream
+ self.codepage_doc = None
+ self.category = None
+ self.presentation_target = None
+ self.bytes = None
+ self.lines = None
+ self.paragraphs = None
+ self.slides = None
+ self.notes = None
+ self.hidden_slides = None
+ self.mm_clips = None
+ self.scale_crop = None
+ self.heading_pairs = None
+ self.titles_of_parts = None
+ self.manager = None
+ self.company = None
+ self.links_dirty = None
+ self.chars_with_spaces = None
+ self.unused = None
+ self.shared_doc = None
+ self.link_base = None
+ self.hlinks = None
+ self.hlinks_changed = None
+ self.version = None
+ self.dig_sig = None
+ self.content_type = None
+ self.content_status = None
+ self.language = None
+ self.doc_version = None
+
+
+ def parse_properties(self, olefile):
+ """
+ Parse standard properties of an OLE file, from the streams
+ "\x05SummaryInformation" and "\x05DocumentSummaryInformation",
+ if present.
+ Properties are converted to strings, integers or python datetime objects.
+ If a property is not present, its value is set to None.
+ """
+ # first set all attributes to None:
+ for attrib in (self.SUMMARY_ATTRIBS + self.DOCSUM_ATTRIBS):
+ setattr(self, attrib, None)
+ if olefile.exists("\x05SummaryInformation"):
+ # get properties from the stream:
+ # (converting timestamps to python datetime, except total_edit_time,
+ # which is property #10)
+ props = olefile.getproperties("\x05SummaryInformation",
+ convert_time=True, no_conversion=[10])
+ # store them into this object's attributes:
+ for i in range(len(self.SUMMARY_ATTRIBS)):
+ # ids for standards properties start at 0x01, until 0x13
+ value = props.get(i+1, None)
+ setattr(self, self.SUMMARY_ATTRIBS[i], value)
+ if olefile.exists("\x05DocumentSummaryInformation"):
+ # get properties from the stream:
+ props = olefile.getproperties("\x05DocumentSummaryInformation",
+ convert_time=True)
+ # store them into this object's attributes:
+ for i in range(len(self.DOCSUM_ATTRIBS)):
+ # ids for standards properties start at 0x01, until 0x13
+ value = props.get(i+1, None)
+ setattr(self, self.DOCSUM_ATTRIBS[i], value)
+
+ def dump(self):
+ """
+ Dump all metadata, for debugging purposes.
+ """
+ print('Properties from SummaryInformation stream:')
+ for prop in self.SUMMARY_ATTRIBS:
+ value = getattr(self, prop)
+ print('- %s: %s' % (prop, repr(value)))
+ print('Properties from DocumentSummaryInformation stream:')
+ for prop in self.DOCSUM_ATTRIBS:
+ value = getattr(self, prop)
+ print('- %s: %s' % (prop, repr(value)))
+
+
+#--- _OleStream ---------------------------------------------------------------
+
+class _OleStream(io.BytesIO):
+ """
+ OLE2 Stream
+
+ Returns a read-only file object which can be used to read
+ the contents of a OLE stream (instance of the BytesIO class).
+ To open a stream, use the openstream method in the OleFile class.
+
+ This function can be used with either ordinary streams,
+ or ministreams, depending on the offset, sectorsize, and
+ fat table arguments.
+
+ Attributes:
+ - size: actual size of data stream, after it was opened.
+ """
+
+ # FIXME: should store the list of sects obtained by following
+ # the fat chain, and load new sectors on demand instead of
+ # loading it all in one go.
+
+ def __init__(self, fp, sect, size, offset, sectorsize, fat, filesize):
+ """
+ Constructor for _OleStream class.
+
+ :param fp : file object, the OLE container or the MiniFAT stream
+ :param sect : sector index of first sector in the stream
+ :param size : total size of the stream
+ :param offset : offset in bytes for the first FAT or MiniFAT sector
+ :param sectorsize: size of one sector
+ :param fat : array/list of sector indexes (FAT or MiniFAT)
+ :param filesize : size of OLE file (for debugging)
+ :returns : a BytesIO instance containing the OLE stream
+ """
+ debug('_OleStream.__init__:')
+ debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s'
+ %(sect,sect,size,offset,sectorsize,len(fat), repr(fp)))
+ #[PL] To detect malformed documents with FAT loops, we compute the
+ # expected number of sectors in the stream:
+ unknown_size = False
+ if size==0x7FFFFFFF:
+ # this is the case when called from OleFileIO._open(), and stream
+ # size is not known in advance (for example when reading the
+ # Directory stream). Then we can only guess maximum size:
+ size = len(fat)*sectorsize
+ # and we keep a record that size was unknown:
+ unknown_size = True
+ debug(' stream with UNKNOWN SIZE')
+ nb_sectors = (size + (sectorsize-1)) // sectorsize
+ debug('nb_sectors = %d' % nb_sectors)
+ # This number should (at least) be less than the total number of
+ # sectors in the given FAT:
+ if nb_sectors > len(fat):
+ raise IOError('malformed OLE document, stream too large')
+ # optimization(?): data is first a list of strings, and join() is called
+ # at the end to concatenate all in one string.
+ # (this may not be really useful with recent Python versions)
+ data = []
+ # if size is zero, then first sector index should be ENDOFCHAIN:
+ if size == 0 and sect != ENDOFCHAIN:
+ debug('size == 0 and sect != ENDOFCHAIN:')
+ raise IOError('incorrect OLE sector index for empty stream')
+ #[PL] A fixed-length for loop is used instead of an undefined while
+ # loop to avoid DoS attacks:
+ for i in range(nb_sectors):
+ # Sector index may be ENDOFCHAIN, but only if size was unknown
+ if sect == ENDOFCHAIN:
+ if unknown_size:
+ break
+ else:
+ # else this means that the stream is smaller than declared:
+ debug('sect=ENDOFCHAIN before expected size')
+ raise IOError('incomplete OLE stream')
+ # sector index should be within FAT:
+ if sect<0 or sect>=len(fat):
+ debug('sect=%d (%X) / len(fat)=%d' % (sect, sect, len(fat)))
+ debug('i=%d / nb_sectors=%d' %(i, nb_sectors))
+## tmp_data = b"".join(data)
+## f = open('test_debug.bin', 'wb')
+## f.write(tmp_data)
+## f.close()
+## debug('data read so far: %d bytes' % len(tmp_data))
+ raise IOError('incorrect OLE FAT, sector index out of range')
+ #TODO: merge this code with OleFileIO.getsect() ?
+ #TODO: check if this works with 4K sectors:
+ try:
+ fp.seek(offset + sectorsize * sect)
+ except:
+ debug('sect=%d, seek=%d, filesize=%d' %
+ (sect, offset+sectorsize*sect, filesize))
+ raise IOError('OLE sector index out of range')
+ sector_data = fp.read(sectorsize)
+ # [PL] check if there was enough data:
+ # Note: if sector is the last of the file, sometimes it is not a
+ # complete sector (of 512 or 4K), so we may read less than
+ # sectorsize.
+ if len(sector_data)!=sectorsize and sect!=(len(fat)-1):
+ debug('sect=%d / len(fat)=%d, seek=%d / filesize=%d, len read=%d' %
+ (sect, len(fat), offset+sectorsize*sect, filesize, len(sector_data)))
+ debug('seek+len(read)=%d' % (offset+sectorsize*sect+len(sector_data)))
+ raise IOError('incomplete OLE sector')
+ data.append(sector_data)
+ # jump to next sector in the FAT:
+ try:
+ sect = fat[sect]
+ except IndexError:
+ # [PL] if pointer is out of the FAT an exception is raised
+ raise IOError('incorrect OLE FAT, sector index out of range')
+ #[PL] Last sector should be a "end of chain" marker:
+ if sect != ENDOFCHAIN:
+ raise IOError('incorrect last sector index in OLE stream')
+ data = b"".join(data)
+ # Data is truncated to the actual stream size:
+ if len(data) >= size:
+ data = data[:size]
+ # actual stream size is stored for future use:
+ self.size = size
+ elif unknown_size:
+ # actual stream size was not known, now we know the size of read
+ # data:
+ self.size = len(data)
+ else:
+ # read data is less than expected:
+ debug('len(data)=%d, size=%d' % (len(data), size))
+ raise IOError('OLE stream size is less than declared')
+ # when all data is read in memory, BytesIO constructor is called
+ io.BytesIO.__init__(self, data)
+ # Then the _OleStream object can be used as a read-only file object.
+
+
+#--- _OleDirectoryEntry -------------------------------------------------------
+
+class _OleDirectoryEntry:
+
+ """
+ OLE2 Directory Entry
+ """
+ #[PL] parsing code moved from OleFileIO.loaddirectory
+
+ # struct to parse directory entries:
+ # <: little-endian byte order, standard sizes
+ # (note: this should guarantee that Q returns a 64 bits int)
+ # 64s: string containing entry name in unicode (max 31 chars) + null char
+ # H: uint16, number of bytes used in name buffer, including null = (len+1)*2
+ # B: uint8, dir entry type (between 0 and 5)
+ # B: uint8, color: 0=black, 1=red
+ # I: uint32, index of left child node in the red-black tree, NOSTREAM if none
+ # I: uint32, index of right child node in the red-black tree, NOSTREAM if none
+ # I: uint32, index of child root node if it is a storage, else NOSTREAM
+ # 16s: CLSID, unique identifier (only used if it is a storage)
+ # I: uint32, user flags
+ # Q (was 8s): uint64, creation timestamp or zero
+ # Q (was 8s): uint64, modification timestamp or zero
+ # I: uint32, SID of first sector if stream or ministream, SID of 1st sector
+ # of stream containing ministreams if root entry, 0 otherwise
+ # I: uint32, total stream size in bytes if stream (low 32 bits), 0 otherwise
+ # I: uint32, total stream size in bytes if stream (high 32 bits), 0 otherwise
+ STRUCT_DIRENTRY = '<64sHBBIII16sIQQIII'
+ # size of a directory entry: 128 bytes
+ DIRENTRY_SIZE = 128
+ assert struct.calcsize(STRUCT_DIRENTRY) == DIRENTRY_SIZE
+
+
+ def __init__(self, entry, sid, olefile):
+ """
+ Constructor for an _OleDirectoryEntry object.
+ Parses a 128-bytes entry from the OLE Directory stream.
+
+ :param entry : string (must be 128 bytes long)
+ :param sid : index of this directory entry in the OLE file directory
+ :param olefile: OleFileIO containing this directory entry
+ """
+ self.sid = sid
+ # ref to olefile is stored for future use
+ self.olefile = olefile
+ # kids is a list of children entries, if this entry is a storage:
+ # (list of _OleDirectoryEntry objects)
+ self.kids = []
+ # kids_dict is a dictionary of children entries, indexed by their
+ # name in lowercase: used to quickly find an entry, and to detect
+ # duplicates
+ self.kids_dict = {}
+ # flag used to detect if the entry is referenced more than once in
+ # directory:
+ self.used = False
+ # decode DirEntry
+ (
+ name,
+ namelength,
+ self.entry_type,
+ self.color,
+ self.sid_left,
+ self.sid_right,
+ self.sid_child,
+ clsid,
+ self.dwUserFlags,
+ self.createTime,
+ self.modifyTime,
+ self.isectStart,
+ sizeLow,
+ sizeHigh
+ ) = struct.unpack(_OleDirectoryEntry.STRUCT_DIRENTRY, entry)
+ if self.entry_type not in [STGTY_ROOT, STGTY_STORAGE, STGTY_STREAM, STGTY_EMPTY]:
+ olefile._raise_defect(DEFECT_INCORRECT, 'unhandled OLE storage type')
+ # only first directory entry can (and should) be root:
+ if self.entry_type == STGTY_ROOT and sid != 0:
+ olefile._raise_defect(DEFECT_INCORRECT, 'duplicate OLE root entry')
+ if sid == 0 and self.entry_type != STGTY_ROOT:
+ olefile._raise_defect(DEFECT_INCORRECT, 'incorrect OLE root entry')
+ #debug (struct.unpack(fmt_entry, entry[:len_entry]))
+ # name should be at most 31 unicode characters + null character,
+ # so 64 bytes in total (31*2 + 2):
+ if namelength>64:
+ olefile._raise_defect(DEFECT_INCORRECT, 'incorrect DirEntry name length')
+ # if exception not raised, namelength is set to the maximum value:
+ namelength = 64
+ # only characters without ending null char are kept:
+ name = name[:(namelength-2)]
+ # name is converted from unicode to Latin-1:
+ self.name = _unicode(name)
+
+ debug('DirEntry SID=%d: %s' % (self.sid, repr(self.name)))
+ debug(' - type: %d' % self.entry_type)
+ debug(' - sect: %d' % self.isectStart)
+ debug(' - SID left: %d, right: %d, child: %d' % (self.sid_left,
+ self.sid_right, self.sid_child))
+
+ # sizeHigh is only used for 4K sectors, it should be zero for 512 bytes
+ # sectors, BUT apparently some implementations set it as 0xFFFFFFFF, 1
+ # or some other value so it cannot be raised as a defect in general:
+ if olefile.sectorsize == 512:
+ if sizeHigh != 0 and sizeHigh != 0xFFFFFFFF:
+ debug('sectorsize=%d, sizeLow=%d, sizeHigh=%d (%X)' %
+ (olefile.sectorsize, sizeLow, sizeHigh, sizeHigh))
+ olefile._raise_defect(DEFECT_UNSURE, 'incorrect OLE stream size')
+ self.size = sizeLow
+ else:
+ self.size = sizeLow + (long(sizeHigh)<<32)
+ debug(' - size: %d (sizeLow=%d, sizeHigh=%d)' % (self.size, sizeLow, sizeHigh))
+
+ self.clsid = _clsid(clsid)
+ # a storage should have a null size, BUT some implementations such as
+ # Word 8 for Mac seem to allow non-null values => Potential defect:
+ if self.entry_type == STGTY_STORAGE and self.size != 0:
+ olefile._raise_defect(DEFECT_POTENTIAL, 'OLE storage with size>0')
+ # check if stream is not already referenced elsewhere:
+ if self.entry_type in (STGTY_ROOT, STGTY_STREAM) and self.size>0:
+ if self.size < olefile.minisectorcutoff \
+ and self.entry_type==STGTY_STREAM: # only streams can be in MiniFAT
+ # ministream object
+ minifat = True
+ else:
+ minifat = False
+ olefile._check_duplicate_stream(self.isectStart, minifat)
+
+
+
+ def build_storage_tree(self):
+ """
+ Read and build the red-black tree attached to this _OleDirectoryEntry
+ object, if it is a storage.
+ Note that this method builds a tree of all subentries, so it should
+ only be called for the root object once.
+ """
+ debug('build_storage_tree: SID=%d - %s - sid_child=%d'
+ % (self.sid, repr(self.name), self.sid_child))
+ if self.sid_child != NOSTREAM:
+ # if child SID is not NOSTREAM, then this entry is a storage.
+ # Let's walk through the tree of children to fill the kids list:
+ self.append_kids(self.sid_child)
+
+ # Note from OpenOffice documentation: the safest way is to
+ # recreate the tree because some implementations may store broken
+ # red-black trees...
+
+ # in the OLE file, entries are sorted on (length, name).
+ # for convenience, we sort them on name instead:
+ # (see rich comparison methods in this class)
+ self.kids.sort()
+
+
+ def append_kids(self, child_sid):
+ """
+ Walk through red-black tree of children of this directory entry to add
+ all of them to the kids list. (recursive method)
+
+ child_sid : index of child directory entry to use, or None when called
+ first time for the root. (only used during recursion)
+ """
+ #[PL] this method was added to use simple recursion instead of a complex
+ # algorithm.
+ # if this is not a storage or a leaf of the tree, nothing to do:
+ if child_sid == NOSTREAM:
+ return
+ # check if child SID is in the proper range:
+ if child_sid<0 or child_sid>=len(self.olefile.direntries):
+ self.olefile._raise_defect(DEFECT_FATAL, 'OLE DirEntry index out of range')
+ # get child direntry:
+ child = self.olefile._load_direntry(child_sid) #direntries[child_sid]
+ debug('append_kids: child_sid=%d - %s - sid_left=%d, sid_right=%d, sid_child=%d'
+ % (child.sid, repr(child.name), child.sid_left, child.sid_right, child.sid_child))
+ # the directory entries are organized as a red-black tree.
+ # (cf. Wikipedia for details)
+ # First walk through left side of the tree:
+ self.append_kids(child.sid_left)
+ # Check if its name is not already used (case-insensitive):
+ name_lower = child.name.lower()
+ if name_lower in self.kids_dict:
+ self.olefile._raise_defect(DEFECT_INCORRECT,
+ "Duplicate filename in OLE storage")
+ # Then the child_sid _OleDirectoryEntry object is appended to the
+ # kids list and dictionary:
+ self.kids.append(child)
+ self.kids_dict[name_lower] = child
+ # Check if kid was not already referenced in a storage:
+ if child.used:
+ self.olefile._raise_defect(DEFECT_INCORRECT,
+ 'OLE Entry referenced more than once')
+ child.used = True
+ # Finally walk through right side of the tree:
+ self.append_kids(child.sid_right)
+ # Afterwards build kid's own tree if it's also a storage:
+ child.build_storage_tree()
+
+
+ def __eq__(self, other):
+ "Compare entries by name"
+ return self.name == other.name
+
+ def __lt__(self, other):
+ "Compare entries by name"
+ return self.name < other.name
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __le__(self, other):
+ return self.__eq__(other) or self.__lt__(other)
+
+ # Reflected __lt__() and __le__() will be used for __gt__() and __ge__()
+
+ #TODO: replace by the same function as MS implementation ?
+ # (order by name length first, then case-insensitive order)
+
+
+ def dump(self, tab = 0):
+ "Dump this entry, and all its subentries (for debug purposes only)"
+ TYPES = ["(invalid)", "(storage)", "(stream)", "(lockbytes)",
+ "(property)", "(root)"]
+ print(" "*tab + repr(self.name), TYPES[self.entry_type], end=' ')
+ if self.entry_type in (STGTY_STREAM, STGTY_ROOT):
+ print(self.size, "bytes", end=' ')
+ print()
+ if self.entry_type in (STGTY_STORAGE, STGTY_ROOT) and self.clsid:
+ print(" "*tab + "{%s}" % self.clsid)
+
+ for kid in self.kids:
+ kid.dump(tab + 2)
+
+
+ def getmtime(self):
+ """
+ Return modification time of a directory entry.
+
+ :returns: None if modification time is null, a python datetime object
+ otherwise (UTC timezone)
+
+ new in version 0.26
+ """
+ if self.modifyTime == 0:
+ return None
+ return filetime2datetime(self.modifyTime)
+
+
+ def getctime(self):
+ """
+ Return creation time of a directory entry.
+
+ :returns: None if modification time is null, a python datetime object
+ otherwise (UTC timezone)
+
+ new in version 0.26
+ """
+ if self.createTime == 0:
+ return None
+ return filetime2datetime(self.createTime)
+
+
+#--- OleFileIO ----------------------------------------------------------------
+
+class OleFileIO:
+ """
+ OLE container object
+
+ This class encapsulates the interface to an OLE 2 structured
+ storage file. Use the :py:meth:`~PIL.OleFileIO.OleFileIO.listdir` and
+ :py:meth:`~PIL.OleFileIO.OleFileIO.openstream` methods to
+ access the contents of this file.
+
+ Object names are given as a list of strings, one for each subentry
+ level. The root entry should be omitted. For example, the following
+ code extracts all image streams from a Microsoft Image Composer file::
+
+ ole = OleFileIO("fan.mic")
+
+ for entry in ole.listdir():
+ if entry[1:2] == "Image":
+ fin = ole.openstream(entry)
+ fout = open(entry[0:1], "wb")
+ while True:
+ s = fin.read(8192)
+ if not s:
+ break
+ fout.write(s)
+
+ You can use the viewer application provided with the Python Imaging
+ Library to view the resulting files (which happens to be standard
+ TIFF files).
+ """
+
+ def __init__(self, filename = None, raise_defects=DEFECT_FATAL):
+ """
+ Constructor for OleFileIO class.
+
+ :param filename: file to open.
+ :param raise_defects: minimal level for defects to be raised as exceptions.
+ (use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a
+ security-oriented application, see source code for details)
+ """
+ # minimal level for defects to be raised as exceptions:
+ self._raise_defects_level = raise_defects
+ # list of defects/issues not raised as exceptions:
+ # tuples of (exception type, message)
+ self.parsing_issues = []
+ if filename:
+ self.open(filename)
+
+
+ def _raise_defect(self, defect_level, message, exception_type=IOError):
+ """
+ This method should be called for any defect found during file parsing.
+ It may raise an IOError exception according to the minimal level chosen
+ for the OleFileIO object.
+
+ :param defect_level: defect level, possible values are:
+ DEFECT_UNSURE : a case which looks weird, but not sure it's a defect
+ DEFECT_POTENTIAL : a potential defect
+ DEFECT_INCORRECT : an error according to specifications, but parsing can go on
+ DEFECT_FATAL : an error which cannot be ignored, parsing is impossible
+ :param message: string describing the defect, used with raised exception.
+ :param exception_type: exception class to be raised, IOError by default
+ """
+ # added by [PL]
+ if defect_level >= self._raise_defects_level:
+ raise exception_type(message)
+ else:
+ # just record the issue, no exception raised:
+ self.parsing_issues.append((exception_type, message))
+
+
+ def open(self, filename):
+ """
+ Open an OLE2 file.
+ Reads the header, FAT and directory.
+
+ :param filename: string-like or file-like object
+ """
+ #[PL] check if filename is a string-like or file-like object:
+ # (it is better to check for a read() method)
+ if hasattr(filename, 'read'):
+ # file-like object
+ self.fp = filename
+ else:
+ # string-like object: filename of file on disk
+ #TODO: if larger than 1024 bytes, this could be the actual data => BytesIO
+ self.fp = open(filename, "rb")
+ # old code fails if filename is not a plain string:
+ #if isinstance(filename, (bytes, basestring)):
+ # self.fp = open(filename, "rb")
+ #else:
+ # self.fp = filename
+ # obtain the filesize by using seek and tell, which should work on most
+ # file-like objects:
+ #TODO: do it above, using getsize with filename when possible?
+ #TODO: fix code to fail with clear exception when filesize cannot be obtained
+ self.fp.seek(0, os.SEEK_END)
+ try:
+ filesize = self.fp.tell()
+ finally:
+ self.fp.seek(0)
+ self._filesize = filesize
+
+ # lists of streams in FAT and MiniFAT, to detect duplicate references
+ # (list of indexes of first sectors of each stream)
+ self._used_streams_fat = []
+ self._used_streams_minifat = []
+
+ header = self.fp.read(512)
+
+ if len(header) != 512 or header[:8] != MAGIC:
+ self._raise_defect(DEFECT_FATAL, "not an OLE2 structured storage file")
+
+ # [PL] header structure according to AAF specifications:
+ ##Header
+ ##struct StructuredStorageHeader { // [offset from start (bytes), length (bytes)]
+ ##BYTE _abSig[8]; // [00H,08] {0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1,
+ ## // 0x1a, 0xe1} for current version
+ ##CLSID _clsid; // [08H,16] reserved must be zero (WriteClassStg/
+ ## // GetClassFile uses root directory class id)
+ ##USHORT _uMinorVersion; // [18H,02] minor version of the format: 33 is
+ ## // written by reference implementation
+ ##USHORT _uDllVersion; // [1AH,02] major version of the dll/format: 3 for
+ ## // 512-byte sectors, 4 for 4 KB sectors
+ ##USHORT _uByteOrder; // [1CH,02] 0xFFFE: indicates Intel byte-ordering
+ ##USHORT _uSectorShift; // [1EH,02] size of sectors in power-of-two;
+ ## // typically 9 indicating 512-byte sectors
+ ##USHORT _uMiniSectorShift; // [20H,02] size of mini-sectors in power-of-two;
+ ## // typically 6 indicating 64-byte mini-sectors
+ ##USHORT _usReserved; // [22H,02] reserved, must be zero
+ ##ULONG _ulReserved1; // [24H,04] reserved, must be zero
+ ##FSINDEX _csectDir; // [28H,04] must be zero for 512-byte sectors,
+ ## // number of SECTs in directory chain for 4 KB
+ ## // sectors
+ ##FSINDEX _csectFat; // [2CH,04] number of SECTs in the FAT chain
+ ##SECT _sectDirStart; // [30H,04] first SECT in the directory chain
+ ##DFSIGNATURE _signature; // [34H,04] signature used for transactions; must
+ ## // be zero. The reference implementation
+ ## // does not support transactions
+ ##ULONG _ulMiniSectorCutoff; // [38H,04] maximum size for a mini stream;
+ ## // typically 4096 bytes
+ ##SECT _sectMiniFatStart; // [3CH,04] first SECT in the MiniFAT chain
+ ##FSINDEX _csectMiniFat; // [40H,04] number of SECTs in the MiniFAT chain
+ ##SECT _sectDifStart; // [44H,04] first SECT in the DIFAT chain
+ ##FSINDEX _csectDif; // [48H,04] number of SECTs in the DIFAT chain
+ ##SECT _sectFat[109]; // [4CH,436] the SECTs of first 109 FAT sectors
+ ##};
+
+ # [PL] header decoding:
+ # '<' indicates little-endian byte ordering for Intel (cf. struct module help)
+ fmt_header = '<8s16sHHHHHHLLLLLLLLLL'
+ header_size = struct.calcsize(fmt_header)
+ debug( "fmt_header size = %d, +FAT = %d" % (header_size, header_size + 109*4) )
+ header1 = header[:header_size]
+ (
+ self.Sig,
+ self.clsid,
+ self.MinorVersion,
+ self.DllVersion,
+ self.ByteOrder,
+ self.SectorShift,
+ self.MiniSectorShift,
+ self.Reserved, self.Reserved1,
+ self.csectDir,
+ self.csectFat,
+ self.sectDirStart,
+ self.signature,
+ self.MiniSectorCutoff,
+ self.MiniFatStart,
+ self.csectMiniFat,
+ self.sectDifStart,
+ self.csectDif
+ ) = struct.unpack(fmt_header, header1)
+ debug( struct.unpack(fmt_header, header1))
+
+ if self.Sig != MAGIC:
+ # OLE signature should always be present
+ self._raise_defect(DEFECT_FATAL, "incorrect OLE signature")
+ if self.clsid != bytearray(16):
+ # according to AAF specs, CLSID should always be zero
+ self._raise_defect(DEFECT_INCORRECT, "incorrect CLSID in OLE header")
+ debug( "MinorVersion = %d" % self.MinorVersion )
+ debug( "DllVersion = %d" % self.DllVersion )
+ if self.DllVersion not in [3, 4]:
+ # version 3: usual format, 512 bytes per sector
+ # version 4: large format, 4K per sector
+ self._raise_defect(DEFECT_INCORRECT, "incorrect DllVersion in OLE header")
+ debug( "ByteOrder = %X" % self.ByteOrder )
+ if self.ByteOrder != 0xFFFE:
+ # For now only common little-endian documents are handled correctly
+ self._raise_defect(DEFECT_FATAL, "incorrect ByteOrder in OLE header")
+ # TODO: add big-endian support for documents created on Mac ?
+ self.SectorSize = 2**self.SectorShift
+ debug( "SectorSize = %d" % self.SectorSize )
+ if self.SectorSize not in [512, 4096]:
+ self._raise_defect(DEFECT_INCORRECT, "incorrect SectorSize in OLE header")
+ if (self.DllVersion==3 and self.SectorSize!=512) \
+ or (self.DllVersion==4 and self.SectorSize!=4096):
+ self._raise_defect(DEFECT_INCORRECT, "SectorSize does not match DllVersion in OLE header")
+ self.MiniSectorSize = 2**self.MiniSectorShift
+ debug( "MiniSectorSize = %d" % self.MiniSectorSize )
+ if self.MiniSectorSize not in [64]:
+ self._raise_defect(DEFECT_INCORRECT, "incorrect MiniSectorSize in OLE header")
+ if self.Reserved != 0 or self.Reserved1 != 0:
+ self._raise_defect(DEFECT_INCORRECT, "incorrect OLE header (non-null reserved bytes)")
+ debug( "csectDir = %d" % self.csectDir )
+ if self.SectorSize==512 and self.csectDir!=0:
+ self._raise_defect(DEFECT_INCORRECT, "incorrect csectDir in OLE header")
+ debug( "csectFat = %d" % self.csectFat )
+ debug( "sectDirStart = %X" % self.sectDirStart )
+ debug( "signature = %d" % self.signature )
+ # Signature should be zero, BUT some implementations do not follow this
+ # rule => only a potential defect:
+ if self.signature != 0:
+ self._raise_defect(DEFECT_POTENTIAL, "incorrect OLE header (signature>0)")
+ debug( "MiniSectorCutoff = %d" % self.MiniSectorCutoff )
+ debug( "MiniFatStart = %X" % self.MiniFatStart )
+ debug( "csectMiniFat = %d" % self.csectMiniFat )
+ debug( "sectDifStart = %X" % self.sectDifStart )
+ debug( "csectDif = %d" % self.csectDif )
+
+ # calculate the number of sectors in the file
+ # (-1 because header doesn't count)
+ self.nb_sect = ( (filesize + self.SectorSize-1) // self.SectorSize) - 1
+ debug( "Number of sectors in the file: %d" % self.nb_sect )
+
+ # file clsid (probably never used, so we don't store it)
+ #clsid = _clsid(header[8:24])
+ self.sectorsize = self.SectorSize #1 << i16(header, 30)
+ self.minisectorsize = self.MiniSectorSize #1 << i16(header, 32)
+ self.minisectorcutoff = self.MiniSectorCutoff # i32(header, 56)
+
+ # check known streams for duplicate references (these are always in FAT,
+ # never in MiniFAT):
+ self._check_duplicate_stream(self.sectDirStart)
+ # check MiniFAT only if it is not empty:
+ if self.csectMiniFat:
+ self._check_duplicate_stream(self.MiniFatStart)
+ # check DIFAT only if it is not empty:
+ if self.csectDif:
+ self._check_duplicate_stream(self.sectDifStart)
+
+ # Load file allocation tables
+ self.loadfat(header)
+ # Load direcory. This sets both the direntries list (ordered by sid)
+ # and the root (ordered by hierarchy) members.
+ self.loaddirectory(self.sectDirStart)#i32(header, 48))
+ self.ministream = None
+ self.minifatsect = self.MiniFatStart #i32(header, 60)
+
+
+ def close(self):
+ """
+ close the OLE file, to release the file object
+ """
+ self.fp.close()
+
+
+ def _check_duplicate_stream(self, first_sect, minifat=False):
+ """
+ Checks if a stream has not been already referenced elsewhere.
+ This method should only be called once for each known stream, and only
+ if stream size is not null.
+ :param first_sect: index of first sector of the stream in FAT
+ :param minifat: if True, stream is located in the MiniFAT, else in the FAT
+ """
+ if minifat:
+ debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect)
+ used_streams = self._used_streams_minifat
+ else:
+ debug('_check_duplicate_stream: sect=%d in FAT' % first_sect)
+ # some values can be safely ignored (not a real stream):
+ if first_sect in (DIFSECT,FATSECT,ENDOFCHAIN,FREESECT):
+ return
+ used_streams = self._used_streams_fat
+ #TODO: would it be more efficient using a dict or hash values, instead
+ # of a list of long ?
+ if first_sect in used_streams:
+ self._raise_defect(DEFECT_INCORRECT, 'Stream referenced twice')
+ else:
+ used_streams.append(first_sect)
+
+
+ def dumpfat(self, fat, firstindex=0):
+ "Displays a part of FAT in human-readable form for debugging purpose"
+ # [PL] added only for debug
+ if not DEBUG_MODE:
+ return
+ # dictionary to convert special FAT values in human-readable strings
+ VPL=8 # valeurs par ligne (8+1 * 8+1 = 81)
+ fatnames = {
+ FREESECT: "..free..",
+ ENDOFCHAIN: "[ END. ]",
+ FATSECT: "FATSECT ",
+ DIFSECT: "DIFSECT "
+ }
+ nbsect = len(fat)
+ nlines = (nbsect+VPL-1)//VPL
+ print("index", end=" ")
+ for i in range(VPL):
+ print("%8X" % i, end=" ")
+ print()
+ for l in range(nlines):
+ index = l*VPL
+ print("%8X:" % (firstindex+index), end=" ")
+ for i in range(index, index+VPL):
+ if i>=nbsect:
+ break
+ sect = fat[i]
+ if sect in fatnames:
+ nom = fatnames[sect]
+ else:
+ if sect == i+1:
+ nom = " --->"
+ else:
+ nom = "%8X" % sect
+ print(nom, end=" ")
+ print()
+
+
+ def dumpsect(self, sector, firstindex=0):
+ "Displays a sector in a human-readable form, for debugging purpose."
+ if not DEBUG_MODE:
+ return
+ VPL=8 # number of values per line (8+1 * 8+1 = 81)
+ tab = array.array(UINT32, sector)
+ nbsect = len(tab)
+ nlines = (nbsect+VPL-1)//VPL
+ print("index", end=" ")
+ for i in range(VPL):
+ print("%8X" % i, end=" ")
+ print()
+ for l in range(nlines):
+ index = l*VPL
+ print("%8X:" % (firstindex+index), end=" ")
+ for i in range(index, index+VPL):
+ if i>=nbsect:
+ break
+ sect = tab[i]
+ nom = "%8X" % sect
+ print(nom, end=" ")
+ print()
+
+ def sect2array(self, sect):
+ """
+ convert a sector to an array of 32 bits unsigned integers,
+ swapping bytes on big endian CPUs such as PowerPC (old Macs)
+ """
+ a = array.array(UINT32, sect)
+ # if CPU is big endian, swap bytes:
+ if sys.byteorder == 'big':
+ a.byteswap()
+ return a
+
+
+ def loadfat_sect(self, sect):
+ """
+ Adds the indexes of the given sector to the FAT
+
+ :param sect: string containing the first FAT sector, or array of long integers
+ :returns: index of last FAT sector.
+ """
+ # a FAT sector is an array of ulong integers.
+ if isinstance(sect, array.array):
+ # if sect is already an array it is directly used
+ fat1 = sect
+ else:
+ # if it's a raw sector, it is parsed in an array
+ fat1 = self.sect2array(sect)
+ self.dumpsect(sect)
+ # The FAT is a sector chain starting at the first index of itself.
+ for isect in fat1:
+ #print("isect = %X" % isect)
+ if isect == ENDOFCHAIN or isect == FREESECT:
+ # the end of the sector chain has been reached
+ break
+ # read the FAT sector
+ s = self.getsect(isect)
+ # parse it as an array of 32 bits integers, and add it to the
+ # global FAT array
+ nextfat = self.sect2array(s)
+ self.fat = self.fat + nextfat
+ return isect
+
+
+ def loadfat(self, header):
+ """
+ Load the FAT table.
+ """
+ # The header contains a sector numbers
+ # for the first 109 FAT sectors. Additional sectors are
+ # described by DIF blocks
+
+ sect = header[76:512]
+ debug( "len(sect)=%d, so %d integers" % (len(sect), len(sect)//4) )
+ #fat = []
+ # [PL] FAT is an array of 32 bits unsigned ints, it's more effective
+ # to use an array than a list in Python.
+ # It's initialized as empty first:
+ self.fat = array.array(UINT32)
+ self.loadfat_sect(sect)
+ #self.dumpfat(self.fat)
+## for i in range(0, len(sect), 4):
+## ix = i32(sect, i)
+## #[PL] if ix == -2 or ix == -1: # ix == 0xFFFFFFFE or ix == 0xFFFFFFFF:
+## if ix == 0xFFFFFFFE or ix == 0xFFFFFFFF:
+## break
+## s = self.getsect(ix)
+## #fat = fat + [i32(s, i) for i in range(0, len(s), 4)]
+## fat = fat + array.array(UINT32, s)
+ if self.csectDif != 0:
+ # [PL] There's a DIFAT because file is larger than 6.8MB
+ # some checks just in case:
+ if self.csectFat <= 109:
+ # there must be at least 109 blocks in header and the rest in
+ # DIFAT, so number of sectors must be >109.
+ self._raise_defect(DEFECT_INCORRECT, 'incorrect DIFAT, not enough sectors')
+ if self.sectDifStart >= self.nb_sect:
+ # initial DIFAT block index must be valid
+ self._raise_defect(DEFECT_FATAL, 'incorrect DIFAT, first index out of range')
+ debug( "DIFAT analysis..." )
+ # We compute the necessary number of DIFAT sectors :
+ # (each DIFAT sector = 127 pointers + 1 towards next DIFAT sector)
+ nb_difat = (self.csectFat-109 + 126)//127
+ debug( "nb_difat = %d" % nb_difat )
+ if self.csectDif != nb_difat:
+ raise IOError('incorrect DIFAT')
+ isect_difat = self.sectDifStart
+ for i in iterrange(nb_difat):
+ debug( "DIFAT block %d, sector %X" % (i, isect_difat) )
+ #TODO: check if corresponding FAT SID = DIFSECT
+ sector_difat = self.getsect(isect_difat)
+ difat = self.sect2array(sector_difat)
+ self.dumpsect(sector_difat)
+ self.loadfat_sect(difat[:127])
+ # last DIFAT pointer is next DIFAT sector:
+ isect_difat = difat[127]
+ debug( "next DIFAT sector: %X" % isect_difat )
+ # checks:
+ if isect_difat not in [ENDOFCHAIN, FREESECT]:
+ # last DIFAT pointer value must be ENDOFCHAIN or FREESECT
+ raise IOError('incorrect end of DIFAT')
+## if len(self.fat) != self.csectFat:
+## # FAT should contain csectFat blocks
+## print("FAT length: %d instead of %d" % (len(self.fat), self.csectFat))
+## raise IOError('incorrect DIFAT')
+ # since FAT is read from fixed-size sectors, it may contain more values
+ # than the actual number of sectors in the file.
+ # Keep only the relevant sector indexes:
+ if len(self.fat) > self.nb_sect:
+ debug('len(fat)=%d, shrunk to nb_sect=%d' % (len(self.fat), self.nb_sect))
+ self.fat = self.fat[:self.nb_sect]
+ debug('\nFAT:')
+ self.dumpfat(self.fat)
+
+
+ def loadminifat(self):
+ """
+ Load the MiniFAT table.
+ """
+ # MiniFAT is stored in a standard sub-stream, pointed to by a header
+ # field.
+ # NOTE: there are two sizes to take into account for this stream:
+ # 1) Stream size is calculated according to the number of sectors
+ # declared in the OLE header. This allocated stream may be more than
+ # needed to store the actual sector indexes.
+ # (self.csectMiniFat is the number of sectors of size self.SectorSize)
+ stream_size = self.csectMiniFat * self.SectorSize
+ # 2) Actually used size is calculated by dividing the MiniStream size
+ # (given by root entry size) by the size of mini sectors, *4 for
+ # 32 bits indexes:
+ nb_minisectors = (self.root.size + self.MiniSectorSize-1) // self.MiniSectorSize
+ used_size = nb_minisectors * 4
+ debug('loadminifat(): minifatsect=%d, nb FAT sectors=%d, used_size=%d, stream_size=%d, nb MiniSectors=%d' %
+ (self.minifatsect, self.csectMiniFat, used_size, stream_size, nb_minisectors))
+ if used_size > stream_size:
+ # This is not really a problem, but may indicate a wrong implementation:
+ self._raise_defect(DEFECT_INCORRECT, 'OLE MiniStream is larger than MiniFAT')
+ # In any case, first read stream_size:
+ s = self._open(self.minifatsect, stream_size, force_FAT=True).read()
+ #[PL] Old code replaced by an array:
+ #self.minifat = [i32(s, i) for i in range(0, len(s), 4)]
+ self.minifat = self.sect2array(s)
+ # Then shrink the array to used size, to avoid indexes out of MiniStream:
+ debug('MiniFAT shrunk from %d to %d sectors' % (len(self.minifat), nb_minisectors))
+ self.minifat = self.minifat[:nb_minisectors]
+ debug('loadminifat(): len=%d' % len(self.minifat))
+ debug('\nMiniFAT:')
+ self.dumpfat(self.minifat)
+
+ def getsect(self, sect):
+ """
+ Read given sector from file on disk.
+
+ :param sect: sector index
+ :returns: a string containing the sector data.
+ """
+ # [PL] this original code was wrong when sectors are 4KB instead of
+ # 512 bytes:
+ #self.fp.seek(512 + self.sectorsize * sect)
+ #[PL]: added safety checks:
+ #print("getsect(%X)" % sect)
+ try:
+ self.fp.seek(self.sectorsize * (sect+1))
+ except:
+ debug('getsect(): sect=%X, seek=%d, filesize=%d' %
+ (sect, self.sectorsize*(sect+1), self._filesize))
+ self._raise_defect(DEFECT_FATAL, 'OLE sector index out of range')
+ sector = self.fp.read(self.sectorsize)
+ if len(sector) != self.sectorsize:
+ debug('getsect(): sect=%X, read=%d, sectorsize=%d' %
+ (sect, len(sector), self.sectorsize))
+ self._raise_defect(DEFECT_FATAL, 'incomplete OLE sector')
+ return sector
+
+
+ def loaddirectory(self, sect):
+ """
+ Load the directory.
+
+ :param sect: sector index of directory stream.
+ """
+ # The directory is stored in a standard
+ # substream, independent of its size.
+
+ # open directory stream as a read-only file:
+ # (stream size is not known in advance)
+ self.directory_fp = self._open(sect)
+
+ #[PL] to detect malformed documents and avoid DoS attacks, the maximum
+ # number of directory entries can be calculated:
+ max_entries = self.directory_fp.size // 128
+ debug('loaddirectory: size=%d, max_entries=%d' %
+ (self.directory_fp.size, max_entries))
+
+ # Create list of directory entries
+ #self.direntries = []
+ # We start with a list of "None" object
+ self.direntries = [None] * max_entries
+## for sid in iterrange(max_entries):
+## entry = fp.read(128)
+## if not entry:
+## break
+## self.direntries.append(_OleDirectoryEntry(entry, sid, self))
+ # load root entry:
+ self._load_direntry(0)
+ # Root entry is the first entry:
+ self.root = self.direntries[0]
+ # read and build all storage trees, starting from the root:
+ self.root.build_storage_tree()
+
+
+ def _load_direntry (self, sid):
+ """
+ Load a directory entry from the directory.
+ This method should only be called once for each storage/stream when
+ loading the directory.
+
+ :param sid: index of storage/stream in the directory.
+ :returns: a _OleDirectoryEntry object
+ :exception IOError: if the entry has always been referenced.
+ """
+ # check if SID is OK:
+ if sid<0 or sid>=len(self.direntries):
+ self._raise_defect(DEFECT_FATAL, "OLE directory index out of range")
+ # check if entry was already referenced:
+ if self.direntries[sid] is not None:
+ self._raise_defect(DEFECT_INCORRECT,
+ "double reference for OLE stream/storage")
+ # if exception not raised, return the object
+ return self.direntries[sid]
+ self.directory_fp.seek(sid * 128)
+ entry = self.directory_fp.read(128)
+ self.direntries[sid] = _OleDirectoryEntry(entry, sid, self)
+ return self.direntries[sid]
+
+
+ def dumpdirectory(self):
+ """
+ Dump directory (for debugging only)
+ """
+ self.root.dump()
+
+
+ def _open(self, start, size = 0x7FFFFFFF, force_FAT=False):
+ """
+ Open a stream, either in FAT or MiniFAT according to its size.
+ (openstream helper)
+
+ :param start: index of first sector
+ :param size: size of stream (or nothing if size is unknown)
+ :param force_FAT: if False (default), stream will be opened in FAT or MiniFAT
+ according to size. If True, it will always be opened in FAT.
+ """
+ debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' %
+ (start, size, str(force_FAT)))
+ # stream size is compared to the MiniSectorCutoff threshold:
+ if size < self.minisectorcutoff and not force_FAT:
+ # ministream object
+ if not self.ministream:
+ # load MiniFAT if it wasn't already done:
+ self.loadminifat()
+ # The first sector index of the miniFAT stream is stored in the
+ # root directory entry:
+ size_ministream = self.root.size
+ debug('Opening MiniStream: sect=%d, size=%d' %
+ (self.root.isectStart, size_ministream))
+ self.ministream = self._open(self.root.isectStart,
+ size_ministream, force_FAT=True)
+ return _OleStream(self.ministream, start, size, 0,
+ self.minisectorsize, self.minifat,
+ self.ministream.size)
+ else:
+ # standard stream
+ return _OleStream(self.fp, start, size, 512,
+ self.sectorsize, self.fat, self._filesize)
+
+
+ def _list(self, files, prefix, node, streams=True, storages=False):
+ """
+ (listdir helper)
+ :param files: list of files to fill in
+ :param prefix: current location in storage tree (list of names)
+ :param node: current node (_OleDirectoryEntry object)
+ :param streams: bool, include streams if True (True by default) - new in v0.26
+ :param storages: bool, include storages if True (False by default) - new in v0.26
+ (note: the root storage is never included)
+ """
+ prefix = prefix + [node.name]
+ for entry in node.kids:
+ if entry.kids:
+ # this is a storage
+ if storages:
+ # add it to the list
+ files.append(prefix[1:] + [entry.name])
+ # check its kids
+ self._list(files, prefix, entry, streams, storages)
+ else:
+ # this is a stream
+ if streams:
+ # add it to the list
+ files.append(prefix[1:] + [entry.name])
+
+
+ def listdir(self, streams=True, storages=False):
+ """
+ Return a list of streams stored in this file
+
+ :param streams: bool, include streams if True (True by default) - new in v0.26
+ :param storages: bool, include storages if True (False by default) - new in v0.26
+ (note: the root storage is never included)
+ """
+ files = []
+ self._list(files, [], self.root, streams, storages)
+ return files
+
+
+ def _find(self, filename):
+ """
+ Returns directory entry of given filename. (openstream helper)
+ Note: this method is case-insensitive.
+
+ :param filename: path of stream in storage tree (except root entry), either:
+
+ - a string using Unix path syntax, for example:
+ 'storage_1/storage_1.2/stream'
+ - a list of storage filenames, path to the desired stream/storage.
+ Example: ['storage_1', 'storage_1.2', 'stream']
+ :returns: sid of requested filename
+ raise IOError if file not found
+ """
+
+ # if filename is a string instead of a list, split it on slashes to
+ # convert to a list:
+ if isinstance(filename, basestring):
+ filename = filename.split('/')
+ # walk across storage tree, following given path:
+ node = self.root
+ for name in filename:
+ for kid in node.kids:
+ if kid.name.lower() == name.lower():
+ break
+ else:
+ raise IOError("file not found")
+ node = kid
+ return node.sid
+
+
+ def openstream(self, filename):
+ """
+ Open a stream as a read-only file object (BytesIO).
+
+ :param filename: path of stream in storage tree (except root entry), either:
+
+ - a string using Unix path syntax, for example:
+ 'storage_1/storage_1.2/stream'
+ - a list of storage filenames, path to the desired stream/storage.
+ Example: ['storage_1', 'storage_1.2', 'stream']
+
+ :returns: file object (read-only)
+ :exception IOError: if filename not found, or if this is not a stream.
+ """
+ sid = self._find(filename)
+ entry = self.direntries[sid]
+ if entry.entry_type != STGTY_STREAM:
+ raise IOError("this file is not a stream")
+ return self._open(entry.isectStart, entry.size)
+
+
+ def get_type(self, filename):
+ """
+ Test if given filename exists as a stream or a storage in the OLE
+ container, and return its type.
+
+ :param filename: path of stream in storage tree. (see openstream for syntax)
+ :returns: False if object does not exist, its entry type (>0) otherwise:
+
+ - STGTY_STREAM: a stream
+ - STGTY_STORAGE: a storage
+ - STGTY_ROOT: the root entry
+ """
+ try:
+ sid = self._find(filename)
+ entry = self.direntries[sid]
+ return entry.entry_type
+ except:
+ return False
+
+
+ def getmtime(self, filename):
+ """
+ Return modification time of a stream/storage.
+
+ :param filename: path of stream/storage in storage tree. (see openstream for
+ syntax)
+ :returns: None if modification time is null, a python datetime object
+ otherwise (UTC timezone)
+
+ new in version 0.26
+ """
+ sid = self._find(filename)
+ entry = self.direntries[sid]
+ return entry.getmtime()
+
+
+ def getctime(self, filename):
+ """
+ Return creation time of a stream/storage.
+
+ :param filename: path of stream/storage in storage tree. (see openstream for
+ syntax)
+ :returns: None if creation time is null, a python datetime object
+ otherwise (UTC timezone)
+
+ new in version 0.26
+ """
+ sid = self._find(filename)
+ entry = self.direntries[sid]
+ return entry.getctime()
+
+
+ def exists(self, filename):
+ """
+ Test if given filename exists as a stream or a storage in the OLE
+ container.
+
+ :param filename: path of stream in storage tree. (see openstream for syntax)
+ :returns: True if object exist, else False.
+ """
+ try:
+ self._find(filename)
+ return True
+ except:
+ return False
+
+
+ def get_size(self, filename):
+ """
+ Return size of a stream in the OLE container, in bytes.
+
+ :param filename: path of stream in storage tree (see openstream for syntax)
+ :returns: size in bytes (long integer)
+ :exception IOError: if file not found
+ :exception TypeError: if this is not a stream
+ """
+ sid = self._find(filename)
+ entry = self.direntries[sid]
+ if entry.entry_type != STGTY_STREAM:
+ #TODO: Should it return zero instead of raising an exception ?
+ raise TypeError('object is not an OLE stream')
+ return entry.size
+
+
+ def get_rootentry_name(self):
+ """
+ Return root entry name. Should usually be 'Root Entry' or 'R' in most
+ implementations.
+ """
+ return self.root.name
+
+
+ def getproperties(self, filename, convert_time=False, no_conversion=None):
+ """
+ Return properties described in substream.
+
+ :param filename: path of stream in storage tree (see openstream for syntax)
+ :param convert_time: bool, if True timestamps will be converted to Python datetime
+ :param no_conversion: None or list of int, timestamps not to be converted
+ (for example total editing time is not a real timestamp)
+ :returns: a dictionary of values indexed by id (integer)
+ """
+ # make sure no_conversion is a list, just to simplify code below:
+ if no_conversion == None:
+ no_conversion = []
+ # stream path as a string to report exceptions:
+ streampath = filename
+ if not isinstance(streampath, str):
+ streampath = '/'.join(streampath)
+
+ fp = self.openstream(filename)
+
+ data = {}
+
+ try:
+ # header
+ s = fp.read(28)
+ #clsid = _clsid(s[8:24])
+
+ # format id
+ s = fp.read(20)
+ #fmtid = _clsid(s[:16])
+ fp.seek(i32(s, 16))
+
+ # get section
+ s = b"****" + fp.read(i32(fp.read(4))-4)
+ # number of properties:
+ num_props = i32(s, 4)
+ except BaseException as exc:
+ # catch exception while parsing property header, and only raise
+ # a DEFECT_INCORRECT then return an empty dict, because this is not
+ # a fatal error when parsing the whole file
+ msg = 'Error while parsing properties header in stream %s: %s' % (
+ repr(streampath), exc)
+ self._raise_defect(DEFECT_INCORRECT, msg, type(exc))
+ return data
+
+ for i in range(num_props):
+ try:
+ id = 0 # just in case of an exception
+ id = i32(s, 8+i*8)
+ offset = i32(s, 12+i*8)
+ type = i32(s, offset)
+
+ debug ('property id=%d: type=%d offset=%X' % (id, type, offset))
+
+ # test for common types first (should perhaps use
+ # a dictionary instead?)
+
+ if type == VT_I2: # 16-bit signed integer
+ value = i16(s, offset+4)
+ if value >= 32768:
+ value = value - 65536
+ elif type == VT_UI2: # 2-byte unsigned integer
+ value = i16(s, offset+4)
+ elif type in (VT_I4, VT_INT, VT_ERROR):
+ # VT_I4: 32-bit signed integer
+ # VT_ERROR: HRESULT, similar to 32-bit signed integer,
+ # see http://msdn.microsoft.com/en-us/library/cc230330.aspx
+ value = i32(s, offset+4)
+ elif type in (VT_UI4, VT_UINT): # 4-byte unsigned integer
+ value = i32(s, offset+4) # FIXME
+ elif type in (VT_BSTR, VT_LPSTR):
+ # CodePageString, see http://msdn.microsoft.com/en-us/library/dd942354.aspx
+ # size is a 32 bits integer, including the null terminator, and
+ # possibly trailing or embedded null chars
+ #TODO: if codepage is unicode, the string should be converted as such
+ count = i32(s, offset+4)
+ value = s[offset+8:offset+8+count-1]
+ # remove all null chars:
+ value = value.replace(b'\x00', b'')
+ elif type == VT_BLOB:
+ # binary large object (BLOB)
+ # see http://msdn.microsoft.com/en-us/library/dd942282.aspx
+ count = i32(s, offset+4)
+ value = s[offset+8:offset+8+count]
+ elif type == VT_LPWSTR:
+ # UnicodeString
+ # see http://msdn.microsoft.com/en-us/library/dd942313.aspx
+ # "the string should NOT contain embedded or additional trailing
+ # null characters."
+ count = i32(s, offset+4)
+ value = _unicode(s[offset+8:offset+8+count*2])
+ elif type == VT_FILETIME:
+ value = long(i32(s, offset+4)) + (long(i32(s, offset+8))<<32)
+ # FILETIME is a 64-bit int: "number of 100ns periods
+ # since Jan 1,1601".
+ if convert_time and id not in no_conversion:
+ debug('Converting property #%d to python datetime, value=%d=%fs'
+ %(id, value, float(value)/10000000))
+ # convert FILETIME to Python datetime.datetime
+ # inspired from http://code.activestate.com/recipes/511425-filetime-to-datetime/
+ _FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0)
+ debug('timedelta days=%d' % (value//(10*1000000*3600*24)))
+ value = _FILETIME_null_date + datetime.timedelta(microseconds=value//10)
+ else:
+ # legacy code kept for backward compatibility: returns a
+ # number of seconds since Jan 1,1601
+ value = value // 10000000 # seconds
+ elif type == VT_UI1: # 1-byte unsigned integer
+ value = i8(s[offset+4])
+ elif type == VT_CLSID:
+ value = _clsid(s[offset+4:offset+20])
+ elif type == VT_CF:
+ # PropertyIdentifier or ClipboardData??
+ # see http://msdn.microsoft.com/en-us/library/dd941945.aspx
+ count = i32(s, offset+4)
+ value = s[offset+8:offset+8+count]
+ elif type == VT_BOOL:
+ # VARIANT_BOOL, 16 bits bool, 0x0000=Fals, 0xFFFF=True
+ # see http://msdn.microsoft.com/en-us/library/cc237864.aspx
+ value = bool(i16(s, offset+4))
+ else:
+ value = None # everything else yields "None"
+ debug ('property id=%d: type=%d not implemented in parser yet' % (id, type))
+
+ # missing: VT_EMPTY, VT_NULL, VT_R4, VT_R8, VT_CY, VT_DATE,
+ # VT_DECIMAL, VT_I1, VT_I8, VT_UI8,
+ # see http://msdn.microsoft.com/en-us/library/dd942033.aspx
+
+ # FIXME: add support for VT_VECTOR
+ # VT_VECTOR is a 32 uint giving the number of items, followed by
+ # the items in sequence. The VT_VECTOR value is combined with the
+ # type of items, e.g. VT_VECTOR|VT_BSTR
+ # see http://msdn.microsoft.com/en-us/library/dd942011.aspx
+
+ #print("%08x" % id, repr(value), end=" ")
+ #print("(%s)" % VT[i32(s, offset) & 0xFFF])
+
+ data[id] = value
+ except BaseException as exc:
+ # catch exception while parsing each property, and only raise
+ # a DEFECT_INCORRECT, because parsing can go on
+ msg = 'Error while parsing property id %d in stream %s: %s' % (
+ id, repr(streampath), exc)
+ self._raise_defect(DEFECT_INCORRECT, msg, type(exc))
+
+ return data
+
+ def get_metadata(self):
+ """
+ Parse standard properties streams, return an OleMetadata object
+ containing all the available metadata.
+ (also stored in the metadata attribute of the OleFileIO object)
+
+ new in version 0.25
+ """
+ self.metadata = OleMetadata()
+ self.metadata.parse_properties(self)
+ return self.metadata
+
+#
+# --------------------------------------------------------------------
+# This script can be used to dump the directory of any OLE2 structured
+# storage file.
+
+if __name__ == "__main__":
+
+ # [PL] display quick usage info if launched from command-line
+ if len(sys.argv) <= 1:
+ print(__doc__)
+ print("""
+Launched from command line, this script parses OLE files and prints info.
+
+Usage: OleFileIO_PL.py [-d] [-c] <file> [file2 ...]
+
+Options:
+-d : debug mode (display a lot of debug information, for developers only)
+-c : check all streams (for debugging purposes)
+""")
+ sys.exit()
+
+ check_streams = False
+ for filename in sys.argv[1:]:
+ #try:
+ # OPTIONS:
+ if filename == '-d':
+ # option to switch debug mode on:
+ set_debug_mode(True)
+ continue
+ if filename == '-c':
+ # option to switch check streams mode on:
+ check_streams = True
+ continue
+
+ ole = OleFileIO(filename)#, raise_defects=DEFECT_INCORRECT)
+ print("-" * 68)
+ print(filename)
+ print("-" * 68)
+ ole.dumpdirectory()
+ for streamname in ole.listdir():
+ if streamname[-1][0] == "\005":
+ print(streamname, ": properties")
+ props = ole.getproperties(streamname, convert_time=True)
+ props = sorted(props.items())
+ for k, v in props:
+ #[PL]: avoid to display too large or binary values:
+ if isinstance(v, (basestring, bytes)):
+ if len(v) > 50:
+ v = v[:50]
+ if isinstance(v, bytes):
+ # quick and dirty binary check:
+ for c in (1,2,3,4,5,6,7,11,12,14,15,16,17,18,19,20,
+ 21,22,23,24,25,26,27,28,29,30,31):
+ if c in bytearray(v):
+ v = '(binary data)'
+ break
+ print(" ", k, v)
+
+ if check_streams:
+ # Read all streams to check if there are errors:
+ print('\nChecking streams...')
+ for streamname in ole.listdir():
+ # print name using repr() to convert binary chars to \xNN:
+ print('-', repr('/'.join(streamname)),'-', end=' ')
+ st_type = ole.get_type(streamname)
+ if st_type == STGTY_STREAM:
+ print('size %d' % ole.get_size(streamname))
+ # just try to read stream in memory:
+ ole.openstream(streamname)
+ else:
+ print('NOT a stream : type=%d' % st_type)
+ print()
+
+## for streamname in ole.listdir():
+## # print name using repr() to convert binary chars to \xNN:
+## print('-', repr('/'.join(streamname)),'-', end=' ')
+## print(ole.getmtime(streamname))
+## print()
+
+ print('Modification/Creation times of all directory entries:')
+ for entry in ole.direntries:
+ if entry is not None:
+ print('- %s: mtime=%s ctime=%s' % (entry.name,
+ entry.getmtime(), entry.getctime()))
+ print()
+
+ # parse and display metadata:
+ meta = ole.get_metadata()
+ meta.dump()
+ print()
+ #[PL] Test a few new methods:
+ root = ole.get_rootentry_name()
+ print('Root entry name: "%s"' % root)
+ if ole.exists('worddocument'):
+ print("This is a Word document.")
+ print("type of stream 'WordDocument':", ole.get_type('worddocument'))
+ print("size :", ole.get_size('worddocument'))
+ if ole.exists('macros/vba'):
+ print("This document may contain VBA macros.")
+
+ # print parsing issues:
+ print('\nNon-fatal issues raised during parsing:')
+ if ole.parsing_issues:
+ for exctype, msg in ole.parsing_issues:
+ print('- %s: %s' % (exctype.__name__, msg))
+ else:
+ print('None')
+## except IOError as v:
+## print("***", "cannot read", file, "-", v)
diff --git a/lib/Python/Lib/PIL/PSDraw.py b/lib/Python/Lib/PIL/PSDraw.py
new file mode 100644
index 000000000..6187e40ad
--- /dev/null
+++ b/lib/Python/Lib/PIL/PSDraw.py
@@ -0,0 +1,237 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# simple postscript graphics interface
+#
+# History:
+# 1996-04-20 fl Created
+# 1999-01-10 fl Added gsave/grestore to image method
+# 2005-05-04 fl Fixed floating point issue in image (from Eric Etheridge)
+#
+# Copyright (c) 1997-2005 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1996 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from __future__ import print_function
+
+from PIL import EpsImagePlugin
+
+
+##
+# Simple Postscript graphics interface.
+
+class PSDraw:
+ """
+ Sets up printing to the given file. If **file** is omitted,
+ :py:attr:`sys.stdout` is assumed.
+ """
+
+ def __init__(self, fp=None):
+ if not fp:
+ import sys
+ fp = sys.stdout
+ self.fp = fp
+
+ def _fp_write(self, to_write):
+ if bytes is str:
+ self.fp.write(to_write)
+ else:
+ self.fp.write(bytes(to_write, 'UTF-8'))
+
+ def begin_document(self, id=None):
+ """Set up printing of a document. (Write Postscript DSC header.)"""
+ # FIXME: incomplete
+ self._fp_write("%!PS-Adobe-3.0\n"
+ "save\n"
+ "/showpage { } def\n"
+ "%%EndComments\n"
+ "%%BeginDocument\n")
+ # self.fp_write(ERROR_PS) # debugging!
+ self._fp_write(EDROFF_PS)
+ self._fp_write(VDI_PS)
+ self._fp_write("%%EndProlog\n")
+ self.isofont = {}
+
+ def end_document(self):
+ """Ends printing. (Write Postscript DSC footer.)"""
+ self._fp_write("%%EndDocument\n"
+ "restore showpage\n"
+ "%%End\n")
+ if hasattr(self.fp, "flush"):
+ self.fp.flush()
+
+ def setfont(self, font, size):
+ """
+ Selects which font to use.
+
+ :param font: A Postscript font name
+ :param size: Size in points.
+ """
+ if font not in self.isofont:
+ # reencode font
+ self._fp_write("/PSDraw-%s ISOLatin1Encoding /%s E\n" %
+ (font, font))
+ self.isofont[font] = 1
+ # rough
+ self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font))
+
+ def line(self, xy0, xy1):
+ """
+ Draws a line between the two points. Coordinates are given in
+ Postscript point coordinates (72 points per inch, (0, 0) is the lower
+ left corner of the page).
+ """
+ xy = xy0 + xy1
+ self._fp_write("%d %d %d %d Vl\n" % xy)
+
+ def rectangle(self, box):
+ """
+ Draws a rectangle.
+
+ :param box: A 4-tuple of integers whose order and function is currently
+ undocumented.
+
+ Hint: the tuple is passed into this format string:
+
+ .. code-block:: python
+
+ %d %d M %d %d 0 Vr\n
+ """
+ self._fp_write("%d %d M %d %d 0 Vr\n" % box)
+
+ def text(self, xy, text):
+ """
+ Draws text at the given position. You must use
+ :py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method.
+ """
+ text = "\\(".join(text.split("("))
+ text = "\\)".join(text.split(")"))
+ xy = xy + (text,)
+ self._fp_write("%d %d M (%s) S\n" % xy)
+
+ def image(self, box, im, dpi=None):
+ """Draw a PIL image, centered in the given box."""
+ # default resolution depends on mode
+ if not dpi:
+ if im.mode == "1":
+ dpi = 200 # fax
+ else:
+ dpi = 100 # greyscale
+ # image size (on paper)
+ x = float(im.size[0] * 72) / dpi
+ y = float(im.size[1] * 72) / dpi
+ # max allowed size
+ xmax = float(box[2] - box[0])
+ ymax = float(box[3] - box[1])
+ if x > xmax:
+ y = y * xmax / x
+ x = xmax
+ if y > ymax:
+ x = x * ymax / y
+ y = ymax
+ dx = (xmax - x) / 2 + box[0]
+ dy = (ymax - y) / 2 + box[1]
+ self._fp_write("gsave\n%f %f translate\n" % (dx, dy))
+ if (x, y) != im.size:
+ # EpsImagePlugin._save prints the image at (0,0,xsize,ysize)
+ sx = x / im.size[0]
+ sy = y / im.size[1]
+ self._fp_write("%f %f scale\n" % (sx, sy))
+ EpsImagePlugin._save(im, self.fp, None, 0)
+ self._fp_write("\ngrestore\n")
+
+# --------------------------------------------------------------------
+# Postscript driver
+
+#
+# EDROFF.PS -- Postscript driver for Edroff 2
+#
+# History:
+# 94-01-25 fl: created (edroff 2.04)
+#
+# Copyright (c) Fredrik Lundh 1994.
+#
+
+EDROFF_PS = """\
+/S { show } bind def
+/P { moveto show } bind def
+/M { moveto } bind def
+/X { 0 rmoveto } bind def
+/Y { 0 exch rmoveto } bind def
+/E { findfont
+ dup maxlength dict begin
+ {
+ 1 index /FID ne { def } { pop pop } ifelse
+ } forall
+ /Encoding exch def
+ dup /FontName exch def
+ currentdict end definefont pop
+} bind def
+/F { findfont exch scalefont dup setfont
+ [ exch /setfont cvx ] cvx bind def
+} bind def
+"""
+
+#
+# VDI.PS -- Postscript driver for VDI meta commands
+#
+# History:
+# 94-01-25 fl: created (edroff 2.04)
+#
+# Copyright (c) Fredrik Lundh 1994.
+#
+
+VDI_PS = """\
+/Vm { moveto } bind def
+/Va { newpath arcn stroke } bind def
+/Vl { moveto lineto stroke } bind def
+/Vc { newpath 0 360 arc closepath } bind def
+/Vr { exch dup 0 rlineto
+ exch dup neg 0 exch rlineto
+ exch neg 0 rlineto
+ 0 exch rlineto
+ 100 div setgray fill 0 setgray } bind def
+/Tm matrix def
+/Ve { Tm currentmatrix pop
+ translate scale newpath 0 0 .5 0 360 arc closepath
+ Tm setmatrix
+} bind def
+/Vf { currentgray exch setgray fill setgray } bind def
+"""
+
+#
+# ERROR.PS -- Error handler
+#
+# History:
+# 89-11-21 fl: created (pslist 1.10)
+#
+
+ERROR_PS = """\
+/landscape false def
+/errorBUF 200 string def
+/errorNL { currentpoint 10 sub exch pop 72 exch moveto } def
+errordict begin /handleerror {
+ initmatrix /Courier findfont 10 scalefont setfont
+ newpath 72 720 moveto $error begin /newerror false def
+ (PostScript Error) show errorNL errorNL
+ (Error: ) show
+ /errorname load errorBUF cvs show errorNL errorNL
+ (Command: ) show
+ /command load dup type /stringtype ne { errorBUF cvs } if show
+ errorNL errorNL
+ (VMstatus: ) show
+ vmstatus errorBUF cvs show ( bytes available, ) show
+ errorBUF cvs show ( bytes used at level ) show
+ errorBUF cvs show errorNL errorNL
+ (Operand stargck: ) show errorNL /ostargck load {
+ dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL
+ } forall errorNL
+ (Execution stargck: ) show errorNL /estargck load {
+ dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL
+ } forall
+ end showpage
+} def end
+"""
diff --git a/lib/Python/Lib/PIL/PaletteFile.py b/lib/Python/Lib/PIL/PaletteFile.py
new file mode 100644
index 000000000..37ba4cbff
--- /dev/null
+++ b/lib/Python/Lib/PIL/PaletteFile.py
@@ -0,0 +1,55 @@
+#
+# Python Imaging Library
+# $Id$
+#
+# stuff to read simple, teragon-style palette files
+#
+# History:
+# 97-08-23 fl Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL._binary import o8
+
+
+##
+# File handler for Teragon-style palette files.
+
+class PaletteFile:
+
+ rawmode = "RGB"
+
+ def __init__(self, fp):
+
+ self.palette = [(i, i, i) for i in range(256)]
+
+ while True:
+
+ s = fp.readline()
+
+ if not s:
+ break
+ if s[0:1] == b"#":
+ continue
+ if len(s) > 100:
+ raise SyntaxError("bad palette file")
+
+ v = [int(x) for x in s.split()]
+ try:
+ [i, r, g, b] = v
+ except ValueError:
+ [i, r] = v
+ g = b = r
+
+ if 0 <= i <= 255:
+ self.palette[i] = o8(r) + o8(g) + o8(b)
+
+ self.palette = b"".join(self.palette)
+
+ def getpalette(self):
+
+ return self.palette, self.rawmode
diff --git a/lib/Python/Lib/PIL/PalmImagePlugin.py b/lib/Python/Lib/PIL/PalmImagePlugin.py
new file mode 100644
index 000000000..bba1de8bb
--- /dev/null
+++ b/lib/Python/Lib/PIL/PalmImagePlugin.py
@@ -0,0 +1,240 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+
+##
+# Image plugin for Palm pixmap images (output only).
+##
+
+__version__ = "1.0"
+
+from PIL import Image, ImageFile, _binary
+
+_Palm8BitColormapValues = (
+ (255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
+ (255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
+ (255, 153, 204), (255, 102, 204), (255, 51, 204), (255, 0, 204),
+ (255, 255, 153), (255, 204, 153), (255, 153, 153), (255, 102, 153),
+ (255, 51, 153), (255, 0, 153), (204, 255, 255), (204, 204, 255),
+ (204, 153, 255), (204, 102, 255), (204, 51, 255), (204, 0, 255),
+ (204, 255, 204), (204, 204, 204), (204, 153, 204), (204, 102, 204),
+ (204, 51, 204), (204, 0, 204), (204, 255, 153), (204, 204, 153),
+ (204, 153, 153), (204, 102, 153), (204, 51, 153), (204, 0, 153),
+ (153, 255, 255), (153, 204, 255), (153, 153, 255), (153, 102, 255),
+ (153, 51, 255), (153, 0, 255), (153, 255, 204), (153, 204, 204),
+ (153, 153, 204), (153, 102, 204), (153, 51, 204), (153, 0, 204),
+ (153, 255, 153), (153, 204, 153), (153, 153, 153), (153, 102, 153),
+ (153, 51, 153), (153, 0, 153), (102, 255, 255), (102, 204, 255),
+ (102, 153, 255), (102, 102, 255), (102, 51, 255), (102, 0, 255),
+ (102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204),
+ (102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153),
+ (102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153),
+ ( 51, 255, 255), ( 51, 204, 255), ( 51, 153, 255), ( 51, 102, 255),
+ ( 51, 51, 255), ( 51, 0, 255), ( 51, 255, 204), ( 51, 204, 204),
+ ( 51, 153, 204), ( 51, 102, 204), ( 51, 51, 204), ( 51, 0, 204),
+ ( 51, 255, 153), ( 51, 204, 153), ( 51, 153, 153), ( 51, 102, 153),
+ ( 51, 51, 153), ( 51, 0, 153), ( 0, 255, 255), ( 0, 204, 255),
+ ( 0, 153, 255), ( 0, 102, 255), ( 0, 51, 255), ( 0, 0, 255),
+ ( 0, 255, 204), ( 0, 204, 204), ( 0, 153, 204), ( 0, 102, 204),
+ ( 0, 51, 204), ( 0, 0, 204), ( 0, 255, 153), ( 0, 204, 153),
+ ( 0, 153, 153), ( 0, 102, 153), ( 0, 51, 153), ( 0, 0, 153),
+ (255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102),
+ (255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51),
+ (255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51),
+ (255, 255, 0), (255, 204, 0), (255, 153, 0), (255, 102, 0),
+ (255, 51, 0), (255, 0, 0), (204, 255, 102), (204, 204, 102),
+ (204, 153, 102), (204, 102, 102), (204, 51, 102), (204, 0, 102),
+ (204, 255, 51), (204, 204, 51), (204, 153, 51), (204, 102, 51),
+ (204, 51, 51), (204, 0, 51), (204, 255, 0), (204, 204, 0),
+ (204, 153, 0), (204, 102, 0), (204, 51, 0), (204, 0, 0),
+ (153, 255, 102), (153, 204, 102), (153, 153, 102), (153, 102, 102),
+ (153, 51, 102), (153, 0, 102), (153, 255, 51), (153, 204, 51),
+ (153, 153, 51), (153, 102, 51), (153, 51, 51), (153, 0, 51),
+ (153, 255, 0), (153, 204, 0), (153, 153, 0), (153, 102, 0),
+ (153, 51, 0), (153, 0, 0), (102, 255, 102), (102, 204, 102),
+ (102, 153, 102), (102, 102, 102), (102, 51, 102), (102, 0, 102),
+ (102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51),
+ (102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0),
+ (102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0),
+ ( 51, 255, 102), ( 51, 204, 102), ( 51, 153, 102), ( 51, 102, 102),
+ ( 51, 51, 102), ( 51, 0, 102), ( 51, 255, 51), ( 51, 204, 51),
+ ( 51, 153, 51), ( 51, 102, 51), ( 51, 51, 51), ( 51, 0, 51),
+ ( 51, 255, 0), ( 51, 204, 0), ( 51, 153, 0), ( 51, 102, 0),
+ ( 51, 51, 0), ( 51, 0, 0), ( 0, 255, 102), ( 0, 204, 102),
+ ( 0, 153, 102), ( 0, 102, 102), ( 0, 51, 102), ( 0, 0, 102),
+ ( 0, 255, 51), ( 0, 204, 51), ( 0, 153, 51), ( 0, 102, 51),
+ ( 0, 51, 51), ( 0, 0, 51), ( 0, 255, 0), ( 0, 204, 0),
+ ( 0, 153, 0), ( 0, 102, 0), ( 0, 51, 0), ( 17, 17, 17),
+ ( 34, 34, 34), ( 68, 68, 68), ( 85, 85, 85), (119, 119, 119),
+ (136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221),
+ (238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128),
+ ( 0, 128, 0), ( 0, 128, 128), ( 0, 0, 0), ( 0, 0, 0),
+ ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
+ ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
+ ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
+ ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
+ ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0),
+ ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0))
+
+
+# so build a prototype image to be used for palette resampling
+def build_prototype_image():
+ image = Image.new("L", (1, len(_Palm8BitColormapValues),))
+ image.putdata(list(range(len(_Palm8BitColormapValues))))
+ palettedata = ()
+ for i in range(len(_Palm8BitColormapValues)):
+ palettedata = palettedata + _Palm8BitColormapValues[i]
+ for i in range(256 - len(_Palm8BitColormapValues)):
+ palettedata = palettedata + (0, 0, 0)
+ image.putpalette(palettedata)
+ return image
+
+Palm8BitColormapImage = build_prototype_image()
+
+# OK, we now have in Palm8BitColormapImage,
+# a "P"-mode image with the right palette
+#
+# --------------------------------------------------------------------
+
+_FLAGS = {
+ "custom-colormap": 0x4000,
+ "is-compressed": 0x8000,
+ "has-transparent": 0x2000,
+ }
+
+_COMPRESSION_TYPES = {
+ "none": 0xFF,
+ "rle": 0x01,
+ "scanline": 0x00,
+ }
+
+o8 = _binary.o8
+o16b = _binary.o16be
+
+
+#
+# --------------------------------------------------------------------
+
+##
+# (Internal) Image save plugin for the Palm format.
+
+def _save(im, fp, filename, check=0):
+
+ if im.mode == "P":
+
+ # we assume this is a color Palm image with the standard colormap,
+ # unless the "info" dict has a "custom-colormap" field
+
+ rawmode = "P"
+ bpp = 8
+ version = 1
+
+ elif (im.mode == "L" and
+ "bpp" in im.encoderinfo and
+ im.encoderinfo["bpp"] in (1, 2, 4)):
+
+ # this is 8-bit grayscale, so we shift it to get the high-order bits,
+ # and invert it because
+ # Palm does greyscale from white (0) to black (1)
+ bpp = im.encoderinfo["bpp"]
+ im = im.point(
+ lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift))
+ # we ignore the palette here
+ im.mode = "P"
+ rawmode = "P;" + str(bpp)
+ version = 1
+
+ elif im.mode == "L" and "bpp" in im.info and im.info["bpp"] in (1, 2, 4):
+
+ # here we assume that even though the inherent mode is 8-bit grayscale,
+ # only the lower bpp bits are significant.
+ # We invert them to match the Palm.
+ bpp = im.info["bpp"]
+ im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval))
+ # we ignore the palette here
+ im.mode = "P"
+ rawmode = "P;" + str(bpp)
+ version = 1
+
+ elif im.mode == "1":
+
+ # monochrome -- write it inverted, as is the Palm standard
+ rawmode = "1;I"
+ bpp = 1
+ version = 0
+
+ else:
+
+ raise IOError("cannot write mode %s as Palm" % im.mode)
+
+ if check:
+ return check
+
+ #
+ # make sure image data is available
+ im.load()
+
+ # write header
+
+ cols = im.size[0]
+ rows = im.size[1]
+
+ rowbytes = int((cols + (16//bpp - 1)) / (16 // bpp)) * 2
+ transparent_index = 0
+ compression_type = _COMPRESSION_TYPES["none"]
+
+ flags = 0
+ if im.mode == "P" and "custom-colormap" in im.info:
+ flags = flags & _FLAGS["custom-colormap"]
+ colormapsize = 4 * 256 + 2
+ colormapmode = im.palette.mode
+ colormap = im.getdata().getpalette()
+ else:
+ colormapsize = 0
+
+ if "offset" in im.info:
+ offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4
+ else:
+ offset = 0
+
+ fp.write(o16b(cols) + o16b(rows) + o16b(rowbytes) + o16b(flags))
+ fp.write(o8(bpp))
+ fp.write(o8(version))
+ fp.write(o16b(offset))
+ fp.write(o8(transparent_index))
+ fp.write(o8(compression_type))
+ fp.write(o16b(0)) # reserved by Palm
+
+ # now write colormap if necessary
+
+ if colormapsize > 0:
+ fp.write(o16b(256))
+ for i in range(256):
+ fp.write(o8(i))
+ if colormapmode == 'RGB':
+ fp.write(
+ o8(colormap[3 * i]) +
+ o8(colormap[3 * i + 1]) +
+ o8(colormap[3 * i + 2]))
+ elif colormapmode == 'RGBA':
+ fp.write(
+ o8(colormap[4 * i]) +
+ o8(colormap[4 * i + 1]) +
+ o8(colormap[4 * i + 2]))
+
+ # now convert data to raw form
+ ImageFile._save(
+ im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))])
+
+ fp.flush()
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_save("Palm", _save)
+
+Image.register_extension("Palm", ".palm")
+
+Image.register_mime("Palm", "image/palm")
diff --git a/lib/Python/Lib/PIL/PcdImagePlugin.py b/lib/Python/Lib/PIL/PcdImagePlugin.py
new file mode 100644
index 000000000..5ce7aa48c
--- /dev/null
+++ b/lib/Python/Lib/PIL/PcdImagePlugin.py
@@ -0,0 +1,79 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# PCD file handling
+#
+# History:
+# 96-05-10 fl Created
+# 96-05-27 fl Added draft mode (128x192, 256x384)
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.1"
+
+
+from PIL import Image, ImageFile, _binary
+
+i8 = _binary.i8
+
+
+##
+# Image plugin for PhotoCD images. This plugin only reads the 768x512
+# image from the file; higher resolutions are encoded in a proprietary
+# encoding.
+
+class PcdImageFile(ImageFile.ImageFile):
+
+ format = "PCD"
+ format_description = "Kodak PhotoCD"
+
+ def _open(self):
+
+ # rough
+ self.fp.seek(2048)
+ s = self.fp.read(2048)
+
+ if s[:4] != b"PCD_":
+ raise SyntaxError("not a PCD file")
+
+ orientation = i8(s[1538]) & 3
+ if orientation == 1:
+ self.tile_post_rotate = 90 # hack
+ elif orientation == 3:
+ self.tile_post_rotate = -90
+
+ self.mode = "RGB"
+ self.size = 768, 512 # FIXME: not correct for rotated images!
+ self.tile = [("pcd", (0, 0)+self.size, 96*2048, None)]
+
+ def draft(self, mode, size):
+
+ if len(self.tile) != 1:
+ return
+
+ d, e, o, a = self.tile[0]
+
+ if size:
+ scale = max(self.size[0] / size[0], self.size[1] / size[1])
+ for s, o in [(4, 0*2048), (2, 0*2048), (1, 96*2048)]:
+ if scale >= s:
+ break
+ # e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1]
+ # self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s)
+
+ self.tile = [(d, e, o, a)]
+
+ return self
+
+#
+# registry
+
+Image.register_open("PCD", PcdImageFile)
+
+Image.register_extension("PCD", ".pcd")
diff --git a/lib/Python/Lib/PIL/PcfFontFile.py b/lib/Python/Lib/PIL/PcfFontFile.py
new file mode 100644
index 000000000..c19a1c532
--- /dev/null
+++ b/lib/Python/Lib/PIL/PcfFontFile.py
@@ -0,0 +1,252 @@
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library
+# $Id$
+#
+# portable compiled font file parser
+#
+# history:
+# 1997-08-19 fl created
+# 2003-09-13 fl fixed loading of unicode fonts
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1997-2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import Image
+from PIL import FontFile
+from PIL import _binary
+
+# --------------------------------------------------------------------
+# declarations
+
+PCF_MAGIC = 0x70636601 # "\x01fcp"
+
+PCF_PROPERTIES = (1 << 0)
+PCF_ACCELERATORS = (1 << 1)
+PCF_METRICS = (1 << 2)
+PCF_BITMAPS = (1 << 3)
+PCF_INK_METRICS = (1 << 4)
+PCF_BDF_ENCODINGS = (1 << 5)
+PCF_SWIDTHS = (1 << 6)
+PCF_GLYPH_NAMES = (1 << 7)
+PCF_BDF_ACCELERATORS = (1 << 8)
+
+BYTES_PER_ROW = [
+ lambda bits: ((bits+7) >> 3),
+ lambda bits: ((bits+15) >> 3) & ~1,
+ lambda bits: ((bits+31) >> 3) & ~3,
+ lambda bits: ((bits+63) >> 3) & ~7,
+]
+
+i8 = _binary.i8
+l16 = _binary.i16le
+l32 = _binary.i32le
+b16 = _binary.i16be
+b32 = _binary.i32be
+
+
+def sz(s, o):
+ return s[o:s.index(b"\0", o)]
+
+
+##
+# Font file plugin for the X11 PCF format.
+
+class PcfFontFile(FontFile.FontFile):
+
+ name = "name"
+
+ def __init__(self, fp):
+
+ magic = l32(fp.read(4))
+ if magic != PCF_MAGIC:
+ raise SyntaxError("not a PCF file")
+
+ FontFile.FontFile.__init__(self)
+
+ count = l32(fp.read(4))
+ self.toc = {}
+ for i in range(count):
+ type = l32(fp.read(4))
+ self.toc[type] = l32(fp.read(4)), l32(fp.read(4)), l32(fp.read(4))
+
+ self.fp = fp
+
+ self.info = self._load_properties()
+
+ metrics = self._load_metrics()
+ bitmaps = self._load_bitmaps(metrics)
+ encoding = self._load_encoding()
+
+ #
+ # create glyph structure
+
+ for ch in range(256):
+ ix = encoding[ch]
+ if ix is not None:
+ x, y, l, r, w, a, d, f = metrics[ix]
+ glyph = (w, 0), (l, d-y, x+l, d), (0, 0, x, y), bitmaps[ix]
+ self.glyph[ch] = glyph
+
+ def _getformat(self, tag):
+
+ format, size, offset = self.toc[tag]
+
+ fp = self.fp
+ fp.seek(offset)
+
+ format = l32(fp.read(4))
+
+ if format & 4:
+ i16, i32 = b16, b32
+ else:
+ i16, i32 = l16, l32
+
+ return fp, format, i16, i32
+
+ def _load_properties(self):
+
+ #
+ # font properties
+
+ properties = {}
+
+ fp, format, i16, i32 = self._getformat(PCF_PROPERTIES)
+
+ nprops = i32(fp.read(4))
+
+ # read property description
+ p = []
+ for i in range(nprops):
+ p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
+ if nprops & 3:
+ fp.seek(4 - (nprops & 3), 1) # pad
+
+ data = fp.read(i32(fp.read(4)))
+
+ for k, s, v in p:
+ k = sz(data, k)
+ if s:
+ v = sz(data, v)
+ properties[k] = v
+
+ return properties
+
+ def _load_metrics(self):
+
+ #
+ # font metrics
+
+ metrics = []
+
+ fp, format, i16, i32 = self._getformat(PCF_METRICS)
+
+ append = metrics.append
+
+ if (format & 0xff00) == 0x100:
+
+ # "compressed" metrics
+ for i in range(i16(fp.read(2))):
+ left = i8(fp.read(1)) - 128
+ right = i8(fp.read(1)) - 128
+ width = i8(fp.read(1)) - 128
+ ascent = i8(fp.read(1)) - 128
+ descent = i8(fp.read(1)) - 128
+ xsize = right - left
+ ysize = ascent + descent
+ append(
+ (xsize, ysize, left, right, width,
+ ascent, descent, 0)
+ )
+
+ else:
+
+ # "jumbo" metrics
+ for i in range(i32(fp.read(4))):
+ left = i16(fp.read(2))
+ right = i16(fp.read(2))
+ width = i16(fp.read(2))
+ ascent = i16(fp.read(2))
+ descent = i16(fp.read(2))
+ attributes = i16(fp.read(2))
+ xsize = right - left
+ ysize = ascent + descent
+ append(
+ (xsize, ysize, left, right, width,
+ ascent, descent, attributes)
+ )
+
+ return metrics
+
+ def _load_bitmaps(self, metrics):
+
+ #
+ # bitmap data
+
+ bitmaps = []
+
+ fp, format, i16, i32 = self._getformat(PCF_BITMAPS)
+
+ nbitmaps = i32(fp.read(4))
+
+ if nbitmaps != len(metrics):
+ raise IOError("Wrong number of bitmaps")
+
+ offsets = []
+ for i in range(nbitmaps):
+ offsets.append(i32(fp.read(4)))
+
+ bitmapSizes = []
+ for i in range(4):
+ bitmapSizes.append(i32(fp.read(4)))
+
+ byteorder = format & 4 # non-zero => MSB
+ bitorder = format & 8 # non-zero => MSB
+ padindex = format & 3
+
+ bitmapsize = bitmapSizes[padindex]
+ offsets.append(bitmapsize)
+
+ data = fp.read(bitmapsize)
+
+ pad = BYTES_PER_ROW[padindex]
+ mode = "1;R"
+ if bitorder:
+ mode = "1"
+
+ for i in range(nbitmaps):
+ x, y, l, r, w, a, d, f = metrics[i]
+ b, e = offsets[i], offsets[i+1]
+ bitmaps.append(
+ Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x))
+ )
+
+ return bitmaps
+
+ def _load_encoding(self):
+
+ # map character code to bitmap index
+ encoding = [None] * 256
+
+ fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
+
+ firstCol, lastCol = i16(fp.read(2)), i16(fp.read(2))
+ firstRow, lastRow = i16(fp.read(2)), i16(fp.read(2))
+
+ default = i16(fp.read(2))
+
+ nencoding = (lastCol - firstCol + 1) * (lastRow - firstRow + 1)
+
+ for i in range(nencoding):
+ encodingOffset = i16(fp.read(2))
+ if encodingOffset != 0xFFFF:
+ try:
+ encoding[i+firstCol] = encodingOffset
+ except IndexError:
+ break # only load ISO-8859-1 glyphs
+
+ return encoding
diff --git a/lib/Python/Lib/PIL/PcxImagePlugin.py b/lib/Python/Lib/PIL/PcxImagePlugin.py
new file mode 100644
index 000000000..0765f099e
--- /dev/null
+++ b/lib/Python/Lib/PIL/PcxImagePlugin.py
@@ -0,0 +1,186 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# PCX file handling
+#
+# This format was originally used by ZSoft's popular PaintBrush
+# program for the IBM PC. It is also supported by many MS-DOS and
+# Windows applications, including the Windows PaintBrush program in
+# Windows 3.
+#
+# history:
+# 1995-09-01 fl Created
+# 1996-05-20 fl Fixed RGB support
+# 1997-01-03 fl Fixed 2-bit and 4-bit support
+# 1999-02-03 fl Fixed 8-bit support (broken in 1.0b1)
+# 1999-02-07 fl Added write support
+# 2002-06-09 fl Made 2-bit and 4-bit support a bit more robust
+# 2002-07-30 fl Seek from to current position, not beginning of file
+# 2003-06-03 fl Extract DPI settings (info["dpi"])
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.6"
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+
+i8 = _binary.i8
+i16 = _binary.i16le
+o8 = _binary.o8
+
+
+def _accept(prefix):
+ return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
+
+
+##
+# Image plugin for Paintbrush images.
+
+class PcxImageFile(ImageFile.ImageFile):
+
+ format = "PCX"
+ format_description = "Paintbrush"
+
+ def _open(self):
+
+ # header
+ s = self.fp.read(128)
+ if not _accept(s):
+ raise SyntaxError("not a PCX file")
+
+ # image
+ bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1
+ if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
+ raise SyntaxError("bad PCX image size")
+ if Image.DEBUG:
+ print ("BBox: %s %s %s %s" % bbox)
+
+ # format
+ version = i8(s[1])
+ bits = i8(s[3])
+ planes = i8(s[65])
+ stride = i16(s, 66)
+ if Image.DEBUG:
+ print ("PCX version %s, bits %s, planes %s, stride %s" %
+ (version, bits, planes, stride))
+
+ self.info["dpi"] = i16(s, 12), i16(s, 14)
+
+ if bits == 1 and planes == 1:
+ mode = rawmode = "1"
+
+ elif bits == 1 and planes in (2, 4):
+ mode = "P"
+ rawmode = "P;%dL" % planes
+ self.palette = ImagePalette.raw("RGB", s[16:64])
+
+ elif version == 5 and bits == 8 and planes == 1:
+ mode = rawmode = "L"
+ # FIXME: hey, this doesn't work with the incremental loader !!!
+ self.fp.seek(-769, 2)
+ s = self.fp.read(769)
+ if len(s) == 769 and i8(s[0]) == 12:
+ # check if the palette is linear greyscale
+ for i in range(256):
+ if s[i*3+1:i*3+4] != o8(i)*3:
+ mode = rawmode = "P"
+ break
+ if mode == "P":
+ self.palette = ImagePalette.raw("RGB", s[1:])
+ self.fp.seek(128)
+
+ elif version == 5 and bits == 8 and planes == 3:
+ mode = "RGB"
+ rawmode = "RGB;L"
+
+ else:
+ raise IOError("unknown PCX mode")
+
+ self.mode = mode
+ self.size = bbox[2]-bbox[0], bbox[3]-bbox[1]
+
+ bbox = (0, 0) + self.size
+ if Image.DEBUG:
+ print ("size: %sx%s" % self.size)
+
+ self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
+
+# --------------------------------------------------------------------
+# save PCX files
+
+SAVE = {
+ # mode: (version, bits, planes, raw mode)
+ "1": (2, 1, 1, "1"),
+ "L": (5, 8, 1, "L"),
+ "P": (5, 8, 1, "P"),
+ "RGB": (5, 8, 3, "RGB;L"),
+}
+
+o16 = _binary.o16le
+
+
+def _save(im, fp, filename, check=0):
+
+ try:
+ version, bits, planes, rawmode = SAVE[im.mode]
+ except KeyError:
+ raise ValueError("Cannot save %s images as PCX" % im.mode)
+
+ if check:
+ return check
+
+ # bytes per plane
+ stride = (im.size[0] * bits + 7) // 8
+ # stride should be even
+ stride += stride % 2
+ # Stride needs to be kept in sync with the PcxEncode.c version.
+ # Ideally it should be passed in in the state, but the bytes value
+ # gets overwritten.
+
+ if Image.DEBUG:
+ print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % (
+ im.size[0], bits, stride))
+
+ # under windows, we could determine the current screen size with
+ # "Image.core.display_mode()[1]", but I think that's overkill...
+
+ screen = im.size
+
+ dpi = 100, 100
+
+ # PCX header
+ fp.write(
+ o8(10) + o8(version) + o8(1) + o8(bits) + o16(0) +
+ o16(0) + o16(im.size[0]-1) + o16(im.size[1]-1) + o16(dpi[0]) +
+ o16(dpi[1]) + b"\0"*24 + b"\xFF"*24 + b"\0" + o8(planes) +
+ o16(stride) + o16(1) + o16(screen[0]) + o16(screen[1]) +
+ b"\0"*54
+ )
+
+ assert fp.tell() == 128
+
+ ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0,
+ (rawmode, bits*planes))])
+
+ if im.mode == "P":
+ # colour palette
+ fp.write(o8(12))
+ fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
+ elif im.mode == "L":
+ # greyscale palette
+ fp.write(o8(12))
+ for i in range(256):
+ fp.write(o8(i)*3)
+
+# --------------------------------------------------------------------
+# registry
+
+Image.register_open("PCX", PcxImageFile, _accept)
+Image.register_save("PCX", _save)
+
+Image.register_extension("PCX", ".pcx")
diff --git a/lib/Python/Lib/PIL/PdfImagePlugin.py b/lib/Python/Lib/PIL/PdfImagePlugin.py
new file mode 100644
index 000000000..5113f099e
--- /dev/null
+++ b/lib/Python/Lib/PIL/PdfImagePlugin.py
@@ -0,0 +1,238 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# PDF (Acrobat) file handling
+#
+# History:
+# 1996-07-16 fl Created
+# 1997-01-18 fl Fixed header
+# 2004-02-21 fl Fixes for 1/L/CMYK images, etc.
+# 2004-02-24 fl Fixes for 1 and P images.
+#
+# Copyright (c) 1997-2004 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1996-1997 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# Image plugin for PDF images (output only).
+##
+
+__version__ = "0.4"
+
+from PIL import Image, ImageFile
+from PIL._binary import i8
+import io
+
+
+#
+# --------------------------------------------------------------------
+
+# object ids:
+# 1. catalogue
+# 2. pages
+# 3. image
+# 4. page
+# 5. page contents
+
+def _obj(fp, obj, **dict):
+ fp.write("%d 0 obj\n" % obj)
+ if dict:
+ fp.write("<<\n")
+ for k, v in dict.items():
+ if v is not None:
+ fp.write("/%s %s\n" % (k, v))
+ fp.write(">>\n")
+
+
+def _endobj(fp):
+ fp.write("endobj\n")
+
+
+##
+# (Internal) Image save plugin for the PDF format.
+
+def _save(im, fp, filename):
+ resolution = im.encoderinfo.get("resolution", 72.0)
+
+ #
+ # make sure image data is available
+ im.load()
+
+ xref = [0]*(5+1) # placeholders
+
+ class TextWriter:
+ def __init__(self, fp):
+ self.fp = fp
+
+ def __getattr__(self, name):
+ return getattr(self.fp, name)
+
+ def write(self, value):
+ self.fp.write(value.encode('latin-1'))
+
+ fp = TextWriter(fp)
+
+ fp.write("%PDF-1.2\n")
+ fp.write("% created by PIL PDF driver " + __version__ + "\n")
+
+ #
+ # Get image characteristics
+
+ width, height = im.size
+
+ # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits)
+ # or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports
+ # Flatedecode (zip compression).
+
+ bits = 8
+ params = None
+
+ if im.mode == "1":
+ filter = "/ASCIIHexDecode"
+ colorspace = "/DeviceGray"
+ procset = "/ImageB" # grayscale
+ bits = 1
+ elif im.mode == "L":
+ filter = "/DCTDecode"
+ # params = "<< /Predictor 15 /Columns %d >>" % (width-2)
+ colorspace = "/DeviceGray"
+ procset = "/ImageB" # grayscale
+ elif im.mode == "P":
+ filter = "/ASCIIHexDecode"
+ colorspace = "[ /Indexed /DeviceRGB 255 <"
+ palette = im.im.getpalette("RGB")
+ for i in range(256):
+ r = i8(palette[i*3])
+ g = i8(palette[i*3+1])
+ b = i8(palette[i*3+2])
+ colorspace += "%02x%02x%02x " % (r, g, b)
+ colorspace += "> ]"
+ procset = "/ImageI" # indexed color
+ elif im.mode == "RGB":
+ filter = "/DCTDecode"
+ colorspace = "/DeviceRGB"
+ procset = "/ImageC" # color images
+ elif im.mode == "CMYK":
+ filter = "/DCTDecode"
+ colorspace = "/DeviceCMYK"
+ procset = "/ImageC" # color images
+ else:
+ raise ValueError("cannot save mode %s" % im.mode)
+
+ #
+ # catalogue
+
+ xref[1] = fp.tell()
+ _obj(
+ fp, 1,
+ Type="/Catalog",
+ Pages="2 0 R")
+ _endobj(fp)
+
+ #
+ # pages
+
+ xref[2] = fp.tell()
+ _obj(
+ fp, 2,
+ Type="/Pages",
+ Count=1,
+ Kids="[4 0 R]")
+ _endobj(fp)
+
+ #
+ # image
+
+ op = io.BytesIO()
+
+ if filter == "/ASCIIHexDecode":
+ if bits == 1:
+ # FIXME: the hex encoder doesn't support packed 1-bit
+ # images; do things the hard way...
+ data = im.tobytes("raw", "1")
+ im = Image.new("L", (len(data), 1), None)
+ im.putdata(data)
+ ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
+ elif filter == "/DCTDecode":
+ Image.SAVE["JPEG"](im, op, filename)
+ elif filter == "/FlateDecode":
+ ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
+ elif filter == "/RunLengthDecode":
+ ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
+ else:
+ raise ValueError("unsupported PDF filter (%s)" % filter)
+
+ xref[3] = fp.tell()
+ _obj(
+ fp, 3,
+ Type="/XObject",
+ Subtype="/Image",
+ Width=width, # * 72.0 / resolution,
+ Height=height, # * 72.0 / resolution,
+ Length=len(op.getvalue()),
+ Filter=filter,
+ BitsPerComponent=bits,
+ DecodeParams=params,
+ ColorSpace=colorspace)
+
+ fp.write("stream\n")
+ fp.fp.write(op.getvalue())
+ fp.write("\nendstream\n")
+
+ _endobj(fp)
+
+ #
+ # page
+
+ xref[4] = fp.tell()
+ _obj(fp, 4)
+ fp.write(
+ "<<\n/Type /Page\n/Parent 2 0 R\n"
+ "/Resources <<\n/ProcSet [ /PDF %s ]\n"
+ "/XObject << /image 3 0 R >>\n>>\n"
+ "/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % (
+ procset,
+ int(width * 72.0 / resolution),
+ int(height * 72.0 / resolution)))
+ _endobj(fp)
+
+ #
+ # page contents
+
+ op = TextWriter(io.BytesIO())
+
+ op.write(
+ "q %d 0 0 %d 0 0 cm /image Do Q\n" % (
+ int(width * 72.0 / resolution),
+ int(height * 72.0 / resolution)))
+
+ xref[5] = fp.tell()
+ _obj(fp, 5, Length=len(op.fp.getvalue()))
+
+ fp.write("stream\n")
+ fp.fp.write(op.fp.getvalue())
+ fp.write("\nendstream\n")
+
+ _endobj(fp)
+
+ #
+ # trailer
+ startxref = fp.tell()
+ fp.write("xref\n0 %d\n0000000000 65535 f \n" % len(xref))
+ for x in xref[1:]:
+ fp.write("%010d 00000 n \n" % x)
+ fp.write("trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\n" % len(xref))
+ fp.write("startxref\n%d\n%%%%EOF\n" % startxref)
+ fp.flush()
+
+#
+# --------------------------------------------------------------------
+
+Image.register_save("PDF", _save)
+
+Image.register_extension("PDF", ".pdf")
+
+Image.register_mime("PDF", "application/pdf")
diff --git a/lib/Python/Lib/PIL/PixarImagePlugin.py b/lib/Python/Lib/PIL/PixarImagePlugin.py
new file mode 100644
index 000000000..ebf4c8c61
--- /dev/null
+++ b/lib/Python/Lib/PIL/PixarImagePlugin.py
@@ -0,0 +1,69 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# PIXAR raster support for PIL
+#
+# history:
+# 97-01-29 fl Created
+#
+# notes:
+# This is incomplete; it is based on a few samples created with
+# Photoshop 2.5 and 3.0, and a summary description provided by
+# Greg Coats <gcoats@labiris.er.usgs.gov>. Hopefully, "L" and
+# "RGBA" support will be added in future versions.
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.1"
+
+from PIL import Image, ImageFile, _binary
+
+#
+# helpers
+
+i16 = _binary.i16le
+i32 = _binary.i32le
+
+
+##
+# Image plugin for PIXAR raster images.
+
+class PixarImageFile(ImageFile.ImageFile):
+
+ format = "PIXAR"
+ format_description = "PIXAR raster image"
+
+ def _open(self):
+
+ # assuming a 4-byte magic label (FIXME: add "_accept" hook)
+ s = self.fp.read(4)
+ if s != b"\200\350\000\000":
+ raise SyntaxError("not a PIXAR file")
+
+ # read rest of header
+ s = s + self.fp.read(508)
+
+ self.size = i16(s[418:420]), i16(s[416:418])
+
+ # get channel/depth descriptions
+ mode = i16(s[424:426]), i16(s[426:428])
+
+ if mode == (14, 2):
+ self.mode = "RGB"
+ # FIXME: to be continued...
+
+ # create tile descriptor (assuming "dumped")
+ self.tile = [("raw", (0, 0)+self.size, 1024, (self.mode, 0, 1))]
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("PIXAR", PixarImageFile)
+
+#
+# FIXME: what's the standard extension?
diff --git a/lib/Python/Lib/PIL/PngImagePlugin.py b/lib/Python/Lib/PIL/PngImagePlugin.py
new file mode 100644
index 000000000..d8593f90d
--- /dev/null
+++ b/lib/Python/Lib/PIL/PngImagePlugin.py
@@ -0,0 +1,807 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# PNG support code
+#
+# See "PNG (Portable Network Graphics) Specification, version 1.0;
+# W3C Recommendation", 1996-10-01, Thomas Boutell (ed.).
+#
+# history:
+# 1996-05-06 fl Created (couldn't resist it)
+# 1996-12-14 fl Upgraded, added read and verify support (0.2)
+# 1996-12-15 fl Separate PNG stream parser
+# 1996-12-29 fl Added write support, added getchunks
+# 1996-12-30 fl Eliminated circular references in decoder (0.3)
+# 1998-07-12 fl Read/write 16-bit images as mode I (0.4)
+# 2001-02-08 fl Added transparency support (from Zircon) (0.5)
+# 2001-04-16 fl Don't close data source in "open" method (0.6)
+# 2004-02-24 fl Don't even pretend to support interlaced files (0.7)
+# 2004-08-31 fl Do basic sanity check on chunk identifiers (0.8)
+# 2004-09-20 fl Added PngInfo chunk container
+# 2004-12-18 fl Added DPI read support (based on code by Niki Spahiev)
+# 2008-08-13 fl Added tRNS support for RGB images
+# 2009-03-06 fl Support for preserving ICC profiles (by Florian Hoech)
+# 2009-03-08 fl Added zTXT support (from Lowell Alleman)
+# 2009-03-29 fl Read interlaced PNG files (from Conrado Porto Lopes Gouvua)
+#
+# Copyright (c) 1997-2009 by Secret Labs AB
+# Copyright (c) 1996 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from __future__ import print_function
+
+__version__ = "0.9"
+
+import re
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+import zlib
+
+i8 = _binary.i8
+i16 = _binary.i16be
+i32 = _binary.i32be
+
+is_cid = re.compile(b"\w\w\w\w").match
+
+
+_MAGIC = b"\211PNG\r\n\032\n"
+
+
+_MODES = {
+ # supported bits/color combinations, and corresponding modes/rawmodes
+ (1, 0): ("1", "1"),
+ (2, 0): ("L", "L;2"),
+ (4, 0): ("L", "L;4"),
+ (8, 0): ("L", "L"),
+ (16, 0): ("I", "I;16B"),
+ (8, 2): ("RGB", "RGB"),
+ (16, 2): ("RGB", "RGB;16B"),
+ (1, 3): ("P", "P;1"),
+ (2, 3): ("P", "P;2"),
+ (4, 3): ("P", "P;4"),
+ (8, 3): ("P", "P"),
+ (8, 4): ("LA", "LA"),
+ (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
+ (8, 6): ("RGBA", "RGBA"),
+ (16, 6): ("RGBA", "RGBA;16B"),
+}
+
+
+_simple_palette = re.compile(b'^\xff+\x00\xff*$')
+
+# Maximum decompressed size for a iTXt or zTXt chunk.
+# Eliminates decompression bombs where compressed chunks can expand 1000x
+MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK
+# Set the maximum total text chunk size.
+MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK
+
+def _safe_zlib_decompress(s):
+ dobj = zlib.decompressobj()
+ plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
+ if dobj.unconsumed_tail:
+ raise ValueError("Decompressed Data Too Large")
+ return plaintext
+
+
+# --------------------------------------------------------------------
+# Support classes. Suitable for PNG and related formats like MNG etc.
+
+class ChunkStream:
+
+ def __init__(self, fp):
+
+ self.fp = fp
+ self.queue = []
+
+ if not hasattr(Image.core, "crc32"):
+ self.crc = self.crc_skip
+
+ def read(self):
+ "Fetch a new chunk. Returns header information."
+
+ if self.queue:
+ cid, pos, length = self.queue[-1]
+ del self.queue[-1]
+ self.fp.seek(pos)
+ else:
+ s = self.fp.read(8)
+ cid = s[4:]
+ pos = self.fp.tell()
+ length = i32(s)
+
+ if not is_cid(cid):
+ raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
+
+ return cid, pos, length
+
+ def close(self):
+ self.queue = self.crc = self.fp = None
+
+ def push(self, cid, pos, length):
+
+ self.queue.append((cid, pos, length))
+
+ def call(self, cid, pos, length):
+ "Call the appropriate chunk handler"
+
+ if Image.DEBUG:
+ print("STREAM", cid, pos, length)
+ return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
+
+ def crc(self, cid, data):
+ "Read and verify checksum"
+
+ crc1 = Image.core.crc32(data, Image.core.crc32(cid))
+ crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
+ if crc1 != crc2:
+ raise SyntaxError("broken PNG file"
+ "(bad header checksum in %s)" % cid)
+
+ def crc_skip(self, cid, data):
+ "Read checksum. Used if the C module is not present"
+
+ self.fp.read(4)
+
+ def verify(self, endchunk=b"IEND"):
+
+ # Simple approach; just calculate checksum for all remaining
+ # blocks. Must be called directly after open.
+
+ cids = []
+
+ while True:
+ cid, pos, length = self.read()
+ if cid == endchunk:
+ break
+ self.crc(cid, ImageFile._safe_read(self.fp, length))
+ cids.append(cid)
+
+ return cids
+
+
+class iTXt(str):
+ """
+ Subclass of string to allow iTXt chunks to look like strings while
+ keeping their extra information
+
+ """
+ @staticmethod
+ def __new__(cls, text, lang, tkey):
+ """
+ :param value: value for this key
+ :param lang: language code
+ :param tkey: UTF-8 version of the key name
+ """
+
+ self = str.__new__(cls, text)
+ self.lang = lang
+ self.tkey = tkey
+ return self
+
+
+class PngInfo:
+ """
+ PNG chunk container (for use with save(pnginfo=))
+
+ """
+
+ def __init__(self):
+ self.chunks = []
+
+ def add(self, cid, data):
+ """Appends an arbitrary chunk. Use with caution.
+
+ :param cid: a byte string, 4 bytes long.
+ :param data: a byte string of the encoded data
+
+ """
+
+ self.chunks.append((cid, data))
+
+ def add_itxt(self, key, value, lang="", tkey="", zip=False):
+ """Appends an iTXt chunk.
+
+ :param key: latin-1 encodable text key name
+ :param value: value for this key
+ :param lang: language code
+ :param tkey: UTF-8 version of the key name
+ :param zip: compression flag
+
+ """
+
+ if not isinstance(key, bytes):
+ key = key.encode("latin-1", "strict")
+ if not isinstance(value, bytes):
+ value = value.encode("utf-8", "strict")
+ if not isinstance(lang, bytes):
+ lang = lang.encode("utf-8", "strict")
+ if not isinstance(tkey, bytes):
+ tkey = tkey.encode("utf-8", "strict")
+
+ if zip:
+ self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" +
+ zlib.compress(value))
+ else:
+ self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" +
+ value)
+
+ def add_text(self, key, value, zip=0):
+ """Appends a text chunk.
+
+ :param key: latin-1 encodable text key name
+ :param value: value for this key, text or an
+ :py:class:`PIL.PngImagePlugin.iTXt` instance
+ :param zip: compression flag
+
+ """
+ if isinstance(value, iTXt):
+ return self.add_itxt(key, value, value.lang, value.tkey, bool(zip))
+
+ # The tEXt chunk stores latin-1 text
+ if not isinstance(value, bytes):
+ try:
+ value = value.encode('latin-1', 'strict')
+ except UnicodeError:
+ return self.add_itxt(key, value, zip=bool(zip))
+
+ if not isinstance(key, bytes):
+ key = key.encode('latin-1', 'strict')
+
+ if zip:
+ self.add(b"zTXt", key + b"\0\0" + zlib.compress(value))
+ else:
+ self.add(b"tEXt", key + b"\0" + value)
+
+
+# --------------------------------------------------------------------
+# PNG image stream (IHDR/IEND)
+
+class PngStream(ChunkStream):
+
+ def __init__(self, fp):
+
+ ChunkStream.__init__(self, fp)
+
+ # local copies of Image attributes
+ self.im_info = {}
+ self.im_text = {}
+ self.im_size = (0, 0)
+ self.im_mode = None
+ self.im_tile = None
+ self.im_palette = None
+
+ self.text_memory = 0
+
+ def check_text_memory(self, chunklen):
+ self.text_memory += chunklen
+ if self.text_memory > MAX_TEXT_MEMORY:
+ raise ValueError("Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" %
+ self.text_memory)
+
+ def chunk_iCCP(self, pos, length):
+
+ # ICC profile
+ s = ImageFile._safe_read(self.fp, length)
+ # according to PNG spec, the iCCP chunk contains:
+ # Profile name 1-79 bytes (character string)
+ # Null separator 1 byte (null character)
+ # Compression method 1 byte (0)
+ # Compressed profile n bytes (zlib with deflate compression)
+ i = s.find(b"\0")
+ if Image.DEBUG:
+ print("iCCP profile name", s[:i])
+ print("Compression method", i8(s[i]))
+ comp_method = i8(s[i])
+ if comp_method != 0:
+ raise SyntaxError("Unknown compression method %s in iCCP chunk" %
+ comp_method)
+ try:
+ icc_profile = _safe_zlib_decompress(s[i+2:])
+ except zlib.error:
+ icc_profile = None # FIXME
+ self.im_info["icc_profile"] = icc_profile
+ return s
+
+ def chunk_IHDR(self, pos, length):
+
+ # image header
+ s = ImageFile._safe_read(self.fp, length)
+ self.im_size = i32(s), i32(s[4:])
+ try:
+ self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))]
+ except:
+ pass
+ if i8(s[12]):
+ self.im_info["interlace"] = 1
+ if i8(s[11]):
+ raise SyntaxError("unknown filter category")
+ return s
+
+ def chunk_IDAT(self, pos, length):
+
+ # image data
+ self.im_tile = [("zip", (0, 0)+self.im_size, pos, self.im_rawmode)]
+ self.im_idat = length
+ raise EOFError
+
+ def chunk_IEND(self, pos, length):
+
+ # end of PNG image
+ raise EOFError
+
+ def chunk_PLTE(self, pos, length):
+
+ # palette
+ s = ImageFile._safe_read(self.fp, length)
+ if self.im_mode == "P":
+ self.im_palette = "RGB", s
+ return s
+
+ def chunk_tRNS(self, pos, length):
+
+ # transparency
+ s = ImageFile._safe_read(self.fp, length)
+ if self.im_mode == "P":
+ if _simple_palette.match(s):
+ i = s.find(b"\0")
+ if i >= 0:
+ self.im_info["transparency"] = i
+ else:
+ self.im_info["transparency"] = s
+ elif self.im_mode == "L":
+ self.im_info["transparency"] = i16(s)
+ elif self.im_mode == "RGB":
+ self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
+ return s
+
+ def chunk_gAMA(self, pos, length):
+
+ # gamma setting
+ s = ImageFile._safe_read(self.fp, length)
+ self.im_info["gamma"] = i32(s) / 100000.0
+ return s
+
+ def chunk_pHYs(self, pos, length):
+
+ # pixels per unit
+ s = ImageFile._safe_read(self.fp, length)
+ px, py = i32(s), i32(s[4:])
+ unit = i8(s[8])
+ if unit == 1: # meter
+ dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5)
+ self.im_info["dpi"] = dpi
+ elif unit == 0:
+ self.im_info["aspect"] = px, py
+ return s
+
+ def chunk_tEXt(self, pos, length):
+
+ # text
+ s = ImageFile._safe_read(self.fp, length)
+ try:
+ k, v = s.split(b"\0", 1)
+ except ValueError:
+ # fallback for broken tEXt tags
+ k = s
+ v = b""
+ if k:
+ if bytes is not str:
+ k = k.decode('latin-1', 'strict')
+ v = v.decode('latin-1', 'replace')
+
+ self.im_info[k] = self.im_text[k] = v
+ self.check_text_memory(len(v))
+
+ return s
+
+ def chunk_zTXt(self, pos, length):
+
+ # compressed text
+ s = ImageFile._safe_read(self.fp, length)
+ try:
+ k, v = s.split(b"\0", 1)
+ except ValueError:
+ k = s
+ v = b""
+ if v:
+ comp_method = i8(v[0])
+ else:
+ comp_method = 0
+ if comp_method != 0:
+ raise SyntaxError("Unknown compression method %s in zTXt chunk" %
+ comp_method)
+ try:
+ v = _safe_zlib_decompress(v[1:])
+ except zlib.error:
+ v = b""
+
+ if k:
+ if bytes is not str:
+ k = k.decode('latin-1', 'strict')
+ v = v.decode('latin-1', 'replace')
+
+ self.im_info[k] = self.im_text[k] = v
+ self.check_text_memory(len(v))
+
+ return s
+
+ def chunk_iTXt(self, pos, length):
+
+ # international text
+ r = s = ImageFile._safe_read(self.fp, length)
+ try:
+ k, r = r.split(b"\0", 1)
+ except ValueError:
+ return s
+ if len(r) < 2:
+ return s
+ cf, cm, r = i8(r[0]), i8(r[1]), r[2:]
+ try:
+ lang, tk, v = r.split(b"\0", 2)
+ except ValueError:
+ return s
+ if cf != 0:
+ if cm == 0:
+ try:
+ v = _safe_zlib_decompress(v)
+ except zlib.error:
+ return s
+ else:
+ return s
+ if bytes is not str:
+ try:
+ k = k.decode("latin-1", "strict")
+ lang = lang.decode("utf-8", "strict")
+ tk = tk.decode("utf-8", "strict")
+ v = v.decode("utf-8", "strict")
+ except UnicodeError:
+ return s
+
+ self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
+ self.check_text_memory(len(v))
+
+ return s
+
+
+# --------------------------------------------------------------------
+# PNG reader
+
+def _accept(prefix):
+ return prefix[:8] == _MAGIC
+
+
+##
+# Image plugin for PNG images.
+
+class PngImageFile(ImageFile.ImageFile):
+
+ format = "PNG"
+ format_description = "Portable network graphics"
+
+ def _open(self):
+
+ if self.fp.read(8) != _MAGIC:
+ raise SyntaxError("not a PNG file")
+
+ #
+ # Parse headers up to the first IDAT chunk
+
+ self.png = PngStream(self.fp)
+
+ while True:
+
+ #
+ # get next chunk
+
+ cid, pos, length = self.png.read()
+
+ try:
+ s = self.png.call(cid, pos, length)
+ except EOFError:
+ break
+ except AttributeError:
+ if Image.DEBUG:
+ print(cid, pos, length, "(unknown)")
+ s = ImageFile._safe_read(self.fp, length)
+
+ self.png.crc(cid, s)
+
+ #
+ # Copy relevant attributes from the PngStream. An alternative
+ # would be to let the PngStream class modify these attributes
+ # directly, but that introduces circular references which are
+ # difficult to break if things go wrong in the decoder...
+ # (believe me, I've tried ;-)
+
+ self.mode = self.png.im_mode
+ self.size = self.png.im_size
+ self.info = self.png.im_info
+ self.text = self.png.im_text # experimental
+ self.tile = self.png.im_tile
+
+ if self.png.im_palette:
+ rawmode, data = self.png.im_palette
+ self.palette = ImagePalette.raw(rawmode, data)
+
+ self.__idat = length # used by load_read()
+
+ def verify(self):
+ "Verify PNG file"
+
+ if self.fp is None:
+ raise RuntimeError("verify must be called directly after open")
+
+ # back up to beginning of IDAT block
+ self.fp.seek(self.tile[0][2] - 8)
+
+ self.png.verify()
+ self.png.close()
+
+ self.fp = None
+
+ def load_prepare(self):
+ "internal: prepare to read PNG file"
+
+ if self.info.get("interlace"):
+ self.decoderconfig = self.decoderconfig + (1,)
+
+ ImageFile.ImageFile.load_prepare(self)
+
+ def load_read(self, read_bytes):
+ "internal: read more image data"
+
+ while self.__idat == 0:
+ # end of chunk, skip forward to next one
+
+ self.fp.read(4) # CRC
+
+ cid, pos, length = self.png.read()
+
+ if cid not in [b"IDAT", b"DDAT"]:
+ self.png.push(cid, pos, length)
+ return b""
+
+ self.__idat = length # empty chunks are allowed
+
+ # read more data from this chunk
+ if read_bytes <= 0:
+ read_bytes = self.__idat
+ else:
+ read_bytes = min(read_bytes, self.__idat)
+
+ self.__idat = self.__idat - read_bytes
+
+ return self.fp.read(read_bytes)
+
+ def load_end(self):
+ "internal: finished reading image data"
+
+ self.png.close()
+ self.png = None
+
+
+# --------------------------------------------------------------------
+# PNG writer
+
+o8 = _binary.o8
+o16 = _binary.o16be
+o32 = _binary.o32be
+
+_OUTMODES = {
+ # supported PIL modes, and corresponding rawmodes/bits/color combinations
+ "1": ("1", b'\x01\x00'),
+ "L;1": ("L;1", b'\x01\x00'),
+ "L;2": ("L;2", b'\x02\x00'),
+ "L;4": ("L;4", b'\x04\x00'),
+ "L": ("L", b'\x08\x00'),
+ "LA": ("LA", b'\x08\x04'),
+ "I": ("I;16B", b'\x10\x00'),
+ "P;1": ("P;1", b'\x01\x03'),
+ "P;2": ("P;2", b'\x02\x03'),
+ "P;4": ("P;4", b'\x04\x03'),
+ "P": ("P", b'\x08\x03'),
+ "RGB": ("RGB", b'\x08\x02'),
+ "RGBA": ("RGBA", b'\x08\x06'),
+}
+
+
+def putchunk(fp, cid, *data):
+ "Write a PNG chunk (including CRC field)"
+
+ data = b"".join(data)
+
+ fp.write(o32(len(data)) + cid)
+ fp.write(data)
+ hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
+ fp.write(o16(hi) + o16(lo))
+
+
+class _idat:
+ # wrap output from the encoder in IDAT chunks
+
+ def __init__(self, fp, chunk):
+ self.fp = fp
+ self.chunk = chunk
+
+ def write(self, data):
+ self.chunk(self.fp, b"IDAT", data)
+
+
+def _save(im, fp, filename, chunk=putchunk, check=0):
+ # save an image to disk (called by the save method)
+
+ mode = im.mode
+
+ if mode == "P":
+
+ #
+ # attempt to minimize storage requirements for palette images
+ if "bits" in im.encoderinfo:
+ # number of bits specified by user
+ colors = 1 << im.encoderinfo["bits"]
+ else:
+ # check palette contents
+ if im.palette:
+ colors = max(min(len(im.palette.getdata()[1])//3, 256), 2)
+ else:
+ colors = 256
+
+ if colors <= 2:
+ bits = 1
+ elif colors <= 4:
+ bits = 2
+ elif colors <= 16:
+ bits = 4
+ else:
+ bits = 8
+ if bits != 8:
+ mode = "%s;%d" % (mode, bits)
+
+ # encoder options
+ if "dictionary" in im.encoderinfo:
+ dictionary = im.encoderinfo["dictionary"]
+ else:
+ dictionary = b""
+
+ im.encoderconfig = ("optimize" in im.encoderinfo,
+ im.encoderinfo.get("compress_level", -1),
+ im.encoderinfo.get("compress_type", -1),
+ dictionary)
+
+ # get the corresponding PNG mode
+ try:
+ rawmode, mode = _OUTMODES[mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as PNG" % mode)
+
+ if check:
+ return check
+
+ #
+ # write minimal PNG file
+
+ fp.write(_MAGIC)
+
+ chunk(fp, b"IHDR",
+ o32(im.size[0]), o32(im.size[1]), # 0: size
+ mode, # 8: depth/type
+ b'\0', # 10: compression
+ b'\0', # 11: filter category
+ b'\0') # 12: interlace flag
+
+ if im.mode == "P":
+ palette_byte_number = (2 ** bits) * 3
+ palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
+ while len(palette_bytes) < palette_byte_number:
+ palette_bytes += b'\0'
+ chunk(fp, b"PLTE", palette_bytes)
+
+ transparency = im.encoderinfo.get('transparency',
+ im.info.get('transparency', None))
+
+ if transparency or transparency == 0:
+ if im.mode == "P":
+ # limit to actual palette size
+ alpha_bytes = 2**bits
+ if isinstance(transparency, bytes):
+ chunk(fp, b"tRNS", transparency[:alpha_bytes])
+ else:
+ transparency = max(0, min(255, transparency))
+ alpha = b'\xFF' * transparency + b'\0'
+ chunk(fp, b"tRNS", alpha[:alpha_bytes])
+ elif im.mode == "L":
+ transparency = max(0, min(65535, transparency))
+ chunk(fp, b"tRNS", o16(transparency))
+ elif im.mode == "RGB":
+ red, green, blue = transparency
+ chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue))
+ else:
+ if "transparency" in im.encoderinfo:
+ # don't bother with transparency if it's an RGBA
+ # and it's in the info dict. It's probably just stale.
+ raise IOError("cannot use transparency for this mode")
+ else:
+ if im.mode == "P" and im.im.getpalettemode() == "RGBA":
+ alpha = im.im.getpalette("RGBA", "A")
+ alpha_bytes = 2**bits
+ chunk(fp, b"tRNS", alpha[:alpha_bytes])
+
+ dpi = im.encoderinfo.get("dpi")
+ if dpi:
+ chunk(fp, b"pHYs",
+ o32(int(dpi[0] / 0.0254 + 0.5)),
+ o32(int(dpi[1] / 0.0254 + 0.5)),
+ b'\x01')
+
+ info = im.encoderinfo.get("pnginfo")
+ if info:
+ for cid, data in info.chunks:
+ chunk(fp, cid, data)
+
+ # ICC profile writing support -- 2008-06-06 Florian Hoech
+ if im.info.get("icc_profile"):
+ # ICC profile
+ # according to PNG spec, the iCCP chunk contains:
+ # Profile name 1-79 bytes (character string)
+ # Null separator 1 byte (null character)
+ # Compression method 1 byte (0)
+ # Compressed profile n bytes (zlib with deflate compression)
+ name = b"ICC Profile"
+ data = name + b"\0\0" + zlib.compress(im.info["icc_profile"])
+ chunk(fp, b"iCCP", data)
+
+ ImageFile._save(im, _idat(fp, chunk),
+ [("zip", (0, 0)+im.size, 0, rawmode)])
+
+ chunk(fp, b"IEND", b"")
+
+ try:
+ fp.flush()
+ except:
+ pass
+
+
+# --------------------------------------------------------------------
+# PNG chunk converter
+
+def getchunks(im, **params):
+ """Return a list of PNG chunks representing this image."""
+
+ class collector:
+ data = []
+
+ def write(self, data):
+ pass
+
+ def append(self, chunk):
+ self.data.append(chunk)
+
+ def append(fp, cid, *data):
+ data = b"".join(data)
+ hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
+ crc = o16(hi) + o16(lo)
+ fp.append((cid, data, crc))
+
+ fp = collector()
+
+ try:
+ im.encoderinfo = params
+ _save(im, fp, None, append)
+ finally:
+ del im.encoderinfo
+
+ return fp.data
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open("PNG", PngImageFile, _accept)
+Image.register_save("PNG", _save)
+
+Image.register_extension("PNG", ".png")
+
+Image.register_mime("PNG", "image/png")
diff --git a/lib/Python/Lib/PIL/PpmImagePlugin.py b/lib/Python/Lib/PIL/PpmImagePlugin.py
new file mode 100644
index 000000000..954832451
--- /dev/null
+++ b/lib/Python/Lib/PIL/PpmImagePlugin.py
@@ -0,0 +1,172 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# PPM support for PIL
+#
+# History:
+# 96-03-24 fl Created
+# 98-03-06 fl Write RGBA images (as RGB, that is)
+#
+# Copyright (c) Secret Labs AB 1997-98.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+import string
+
+from PIL import Image, ImageFile
+
+#
+# --------------------------------------------------------------------
+
+b_whitespace = string.whitespace
+try:
+ import locale
+ locale_lang, locale_enc = locale.getlocale()
+ if locale_enc is None:
+ locale_lang, locale_enc = locale.getdefaultlocale()
+ b_whitespace = b_whitespace.decode(locale_enc)
+except:
+ pass
+b_whitespace = b_whitespace.encode('ascii', 'ignore')
+
+MODES = {
+ # standard
+ b"P4": "1",
+ b"P5": "L",
+ b"P6": "RGB",
+ # extensions
+ b"P0CMYK": "CMYK",
+ # PIL extensions (for test purposes only)
+ b"PyP": "P",
+ b"PyRGBA": "RGBA",
+ b"PyCMYK": "CMYK"
+}
+
+
+def _accept(prefix):
+ return prefix[0:1] == b"P" and prefix[1] in b"0456y"
+
+
+##
+# Image plugin for PBM, PGM, and PPM images.
+
+class PpmImageFile(ImageFile.ImageFile):
+
+ format = "PPM"
+ format_description = "Pbmplus image"
+
+ def _token(self, s=b""):
+ while True: # read until next whitespace
+ c = self.fp.read(1)
+ if not c or c in b_whitespace:
+ break
+ if c > b'\x79':
+ raise ValueError("Expected ASCII value, found binary")
+ s = s + c
+ if (len(s) > 9):
+ raise ValueError("Expected int, got > 9 digits")
+ return s
+
+ def _open(self):
+
+ # check magic
+ s = self.fp.read(1)
+ if s != b"P":
+ raise SyntaxError("not a PPM file")
+ mode = MODES[self._token(s)]
+
+ if mode == "1":
+ self.mode = "1"
+ rawmode = "1;I"
+ else:
+ self.mode = rawmode = mode
+
+ for ix in range(3):
+ while True:
+ while True:
+ s = self.fp.read(1)
+ if s not in b_whitespace:
+ break
+ if s != b"#":
+ break
+ s = self.fp.readline()
+ s = int(self._token(s))
+ if ix == 0:
+ xsize = s
+ elif ix == 1:
+ ysize = s
+ if mode == "1":
+ break
+ elif ix == 2:
+ # maxgrey
+ if s > 255:
+ if not mode == 'L':
+ raise ValueError("Too many colors for band: %s" % s)
+ if s < 2**16:
+ self.mode = 'I'
+ rawmode = 'I;16B'
+ else:
+ self.mode = 'I'
+ rawmode = 'I;32B'
+
+ self.size = xsize, ysize
+ self.tile = [("raw",
+ (0, 0, xsize, ysize),
+ self.fp.tell(),
+ (rawmode, 0, 1))]
+
+ # ALTERNATIVE: load via builtin debug function
+ # self.im = Image.core.open_ppm(self.filename)
+ # self.mode = self.im.mode
+ # self.size = self.im.size
+
+
+#
+# --------------------------------------------------------------------
+
+def _save(im, fp, filename):
+ if im.mode == "1":
+ rawmode, head = "1;I", b"P4"
+ elif im.mode == "L":
+ rawmode, head = "L", b"P5"
+ elif im.mode == "I":
+ if im.getextrema()[1] < 2**16:
+ rawmode, head = "I;16B", b"P5"
+ else:
+ rawmode, head = "I;32B", b"P5"
+ elif im.mode == "RGB":
+ rawmode, head = "RGB", b"P6"
+ elif im.mode == "RGBA":
+ rawmode, head = "RGB", b"P6"
+ else:
+ raise IOError("cannot write mode %s as PPM" % im.mode)
+ fp.write(head + ("\n%d %d\n" % im.size).encode('ascii'))
+ if head == b"P6":
+ fp.write(b"255\n")
+ if head == b"P5":
+ if rawmode == "L":
+ fp.write(b"255\n")
+ elif rawmode == "I;16B":
+ fp.write(b"65535\n")
+ elif rawmode == "I;32B":
+ fp.write(b"2147483648\n")
+ ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
+
+ # ALTERNATIVE: save via builtin debug function
+ # im._dump(filename)
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open("PPM", PpmImageFile, _accept)
+Image.register_save("PPM", _save)
+
+Image.register_extension("PPM", ".pbm")
+Image.register_extension("PPM", ".pgm")
+Image.register_extension("PPM", ".ppm")
diff --git a/lib/Python/Lib/PIL/PsdImagePlugin.py b/lib/Python/Lib/PIL/PsdImagePlugin.py
new file mode 100644
index 000000000..02c94a860
--- /dev/null
+++ b/lib/Python/Lib/PIL/PsdImagePlugin.py
@@ -0,0 +1,304 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# Adobe PSD 2.5/3.0 file handling
+#
+# History:
+# 1995-09-01 fl Created
+# 1997-01-03 fl Read most PSD images
+# 1997-01-18 fl Fixed P and CMYK support
+# 2001-10-21 fl Added seek/tell support (for layers)
+#
+# Copyright (c) 1997-2001 by Secret Labs AB.
+# Copyright (c) 1995-2001 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.4"
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+
+MODES = {
+ # (photoshop mode, bits) -> (pil mode, required channels)
+ (0, 1): ("1", 1),
+ (0, 8): ("L", 1),
+ (1, 8): ("L", 1),
+ (2, 8): ("P", 1),
+ (3, 8): ("RGB", 3),
+ (4, 8): ("CMYK", 4),
+ (7, 8): ("L", 1), # FIXME: multilayer
+ (8, 8): ("L", 1), # duotone
+ (9, 8): ("LAB", 3)
+}
+
+#
+# helpers
+
+i8 = _binary.i8
+i16 = _binary.i16be
+i32 = _binary.i32be
+
+
+# --------------------------------------------------------------------.
+# read PSD images
+
+def _accept(prefix):
+ return prefix[:4] == b"8BPS"
+
+
+##
+# Image plugin for Photoshop images.
+
+class PsdImageFile(ImageFile.ImageFile):
+
+ format = "PSD"
+ format_description = "Adobe Photoshop"
+
+ def _open(self):
+
+ read = self.fp.read
+
+ #
+ # header
+
+ s = read(26)
+ if s[:4] != b"8BPS" or i16(s[4:]) != 1:
+ raise SyntaxError("not a PSD file")
+
+ psd_bits = i16(s[22:])
+ psd_channels = i16(s[12:])
+ psd_mode = i16(s[24:])
+
+ mode, channels = MODES[(psd_mode, psd_bits)]
+
+ if channels > psd_channels:
+ raise IOError("not enough channels")
+
+ self.mode = mode
+ self.size = i32(s[18:]), i32(s[14:])
+
+ #
+ # color mode data
+
+ size = i32(read(4))
+ if size:
+ data = read(size)
+ if mode == "P" and size == 768:
+ self.palette = ImagePalette.raw("RGB;L", data)
+
+ #
+ # image resources
+
+ self.resources = []
+
+ size = i32(read(4))
+ if size:
+ # load resources
+ end = self.fp.tell() + size
+ while self.fp.tell() < end:
+ signature = read(4)
+ id = i16(read(2))
+ name = read(i8(read(1)))
+ if not (len(name) & 1):
+ read(1) # padding
+ data = read(i32(read(4)))
+ if (len(data) & 1):
+ read(1) # padding
+ self.resources.append((id, name, data))
+ if id == 1039: # ICC profile
+ self.info["icc_profile"] = data
+
+ #
+ # layer and mask information
+
+ self.layers = []
+
+ size = i32(read(4))
+ if size:
+ end = self.fp.tell() + size
+ size = i32(read(4))
+ if size:
+ self.layers = _layerinfo(self.fp)
+ self.fp.seek(end)
+
+ #
+ # image descriptor
+
+ self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels)
+
+ # keep the file open
+ self._fp = self.fp
+ self.frame = 0
+
+ def seek(self, layer):
+ # seek to given layer (1..max)
+ if layer == self.frame:
+ return
+ try:
+ if layer <= 0:
+ raise IndexError
+ name, mode, bbox, tile = self.layers[layer-1]
+ self.mode = mode
+ self.tile = tile
+ self.frame = layer
+ self.fp = self._fp
+ return name, bbox
+ except IndexError:
+ raise EOFError("no such layer")
+
+ def tell(self):
+ # return layer number (0=image, 1..max=layers)
+ return self.frame
+
+ def load_prepare(self):
+ # create image memory if necessary
+ if not self.im or\
+ self.im.mode != self.mode or self.im.size != self.size:
+ self.im = Image.core.fill(self.mode, self.size, 0)
+ # create palette (optional)
+ if self.mode == "P":
+ Image.Image.load(self)
+
+
+def _layerinfo(file):
+ # read layerinfo block
+ layers = []
+ read = file.read
+ for i in range(abs(i16(read(2)))):
+
+ # bounding box
+ y0 = i32(read(4))
+ x0 = i32(read(4))
+ y1 = i32(read(4))
+ x1 = i32(read(4))
+
+ # image info
+ info = []
+ mode = []
+ types = list(range(i16(read(2))))
+ if len(types) > 4:
+ continue
+
+ for i in types:
+ type = i16(read(2))
+
+ if type == 65535:
+ m = "A"
+ else:
+ m = "RGBA"[type]
+
+ mode.append(m)
+ size = i32(read(4))
+ info.append((m, size))
+
+ # figure out the image mode
+ mode.sort()
+ if mode == ["R"]:
+ mode = "L"
+ elif mode == ["B", "G", "R"]:
+ mode = "RGB"
+ elif mode == ["A", "B", "G", "R"]:
+ mode = "RGBA"
+ else:
+ mode = None # unknown
+
+ # skip over blend flags and extra information
+ filler = read(12)
+ name = ""
+ size = i32(read(4))
+ combined = 0
+ if size:
+ length = i32(read(4))
+ if length:
+ mask_y = i32(read(4))
+ mask_x = i32(read(4))
+ mask_h = i32(read(4)) - mask_y
+ mask_w = i32(read(4)) - mask_x
+ file.seek(length - 16, 1)
+ combined += length + 4
+
+ length = i32(read(4))
+ if length:
+ file.seek(length, 1)
+ combined += length + 4
+
+ length = i8(read(1))
+ if length:
+ # Don't know the proper encoding,
+ # Latin-1 should be a good guess
+ name = read(length).decode('latin-1', 'replace')
+ combined += length + 1
+
+ file.seek(size - combined, 1)
+ layers.append((name, mode, (x0, y0, x1, y1)))
+
+ # get tiles
+ i = 0
+ for name, mode, bbox in layers:
+ tile = []
+ for m in mode:
+ t = _maketile(file, m, bbox, 1)
+ if t:
+ tile.extend(t)
+ layers[i] = name, mode, bbox, tile
+ i += 1
+
+ return layers
+
+
+def _maketile(file, mode, bbox, channels):
+
+ tile = None
+ read = file.read
+
+ compression = i16(read(2))
+
+ xsize = bbox[2] - bbox[0]
+ ysize = bbox[3] - bbox[1]
+
+ offset = file.tell()
+
+ if compression == 0:
+ #
+ # raw compression
+ tile = []
+ for channel in range(channels):
+ layer = mode[channel]
+ if mode == "CMYK":
+ layer += ";I"
+ tile.append(("raw", bbox, offset, layer))
+ offset = offset + xsize*ysize
+
+ elif compression == 1:
+ #
+ # packbits compression
+ i = 0
+ tile = []
+ bytecount = read(channels * ysize * 2)
+ offset = file.tell()
+ for channel in range(channels):
+ layer = mode[channel]
+ if mode == "CMYK":
+ layer += ";I"
+ tile.append(
+ ("packbits", bbox, offset, layer)
+ )
+ for y in range(ysize):
+ offset = offset + i16(bytecount[i:i+2])
+ i += 2
+
+ file.seek(offset)
+
+ if offset & 1:
+ read(1) # padding
+
+ return tile
+
+# --------------------------------------------------------------------
+# registry
+
+Image.register_open("PSD", PsdImageFile, _accept)
+
+Image.register_extension("PSD", ".psd")
diff --git a/lib/Python/Lib/PIL/PyAccess.py b/lib/Python/Lib/PIL/PyAccess.py
new file mode 100644
index 000000000..a3f1c3909
--- /dev/null
+++ b/lib/Python/Lib/PIL/PyAccess.py
@@ -0,0 +1,315 @@
+#
+# The Python Imaging Library
+# Pillow fork
+#
+# Python implementation of the PixelAccess Object
+#
+# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1995-2009 by Fredrik Lundh.
+# Copyright (c) 2013 Eric Soroos
+#
+# See the README file for information on usage and redistribution
+#
+
+# Notes:
+#
+# * Implements the pixel access object following Access.
+# * Does not implement the line functions, as they don't appear to be used
+# * Taking only the tuple form, which is used from python.
+# * Fill.c uses the integer form, but it's still going to use the old
+# Access.c implementation.
+#
+
+from __future__ import print_function
+
+from cffi import FFI
+import sys
+
+DEBUG = 0
+
+defs = """
+struct Pixel_RGBA {
+ unsigned char r,g,b,a;
+};
+struct Pixel_I16 {
+ unsigned char l,r;
+};
+"""
+ffi = FFI()
+ffi.cdef(defs)
+
+
+class PyAccess(object):
+
+ def __init__(self, img, readonly=False):
+ vals = dict(img.im.unsafe_ptrs)
+ self.readonly = readonly
+ self.image8 = ffi.cast('unsigned char **', vals['image8'])
+ self.image32 = ffi.cast('int **', vals['image32'])
+ self.image = ffi.cast('unsigned char **', vals['image'])
+ self.xsize = vals['xsize']
+ self.ysize = vals['ysize']
+
+ if DEBUG:
+ print (vals)
+ self._post_init()
+
+ def _post_init(self):
+ pass
+
+ def __setitem__(self, xy, color):
+ """
+ Modifies the pixel at x,y. The color is given as a single
+ numerical value for single band images, and a tuple for
+ multi-band images
+
+ :param xy: The pixel coordinate, given as (x, y).
+ :param value: The pixel value.
+ """
+ if self.readonly:
+ raise ValueError('Attempt to putpixel a read only image')
+ (x, y) = self.check_xy(xy)
+ return self.set_pixel(x, y, color)
+
+ def __getitem__(self, xy):
+ """
+ Returns the pixel at x,y. The pixel is returned as a single
+ value for single band images or a tuple for multiple band
+ images
+
+ :param xy: The pixel coordinate, given as (x, y).
+ :returns: a pixel value for single band images, a tuple of
+ pixel values for multiband images.
+ """
+
+ (x, y) = self.check_xy(xy)
+ return self.get_pixel(x, y)
+
+ putpixel = __setitem__
+ getpixel = __getitem__
+
+ def check_xy(self, xy):
+ (x, y) = xy
+ if not (0 <= x < self.xsize and 0 <= y < self.ysize):
+ raise ValueError('pixel location out of range')
+ return xy
+
+
+class _PyAccess32_2(PyAccess):
+ """ PA, LA, stored in first and last bytes of a 32 bit word """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return (pixel.r, pixel.a)
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ # tuple
+ pixel.r = min(color[0], 255)
+ pixel.a = min(color[1], 255)
+
+
+class _PyAccess32_3(PyAccess):
+ """ RGB and friends, stored in the first three bytes of a 32 bit word """
+
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return (pixel.r, pixel.g, pixel.b)
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ # tuple
+ pixel.r = min(color[0], 255)
+ pixel.g = min(color[1], 255)
+ pixel.b = min(color[2], 255)
+
+
+class _PyAccess32_4(PyAccess):
+ """ RGBA etc, all 4 bytes of a 32 bit word """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return (pixel.r, pixel.g, pixel.b, pixel.a)
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ # tuple
+ pixel.r = min(color[0], 255)
+ pixel.g = min(color[1], 255)
+ pixel.b = min(color[2], 255)
+ pixel.a = min(color[3], 255)
+
+
+class _PyAccess8(PyAccess):
+ """ 1, L, P, 8 bit images stored as uint8 """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = self.image8
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ try:
+ # integer
+ self.pixels[y][x] = min(color, 255)
+ except:
+ # tuple
+ self.pixels[y][x] = min(color[0], 255)
+
+
+class _PyAccessI16_N(PyAccess):
+ """ I;16 access, native bitendian without conversion """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast('unsigned short **', self.image)
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ try:
+ # integer
+ self.pixels[y][x] = min(color, 65535)
+ except:
+ # tuple
+ self.pixels[y][x] = min(color[0], 65535)
+
+
+class _PyAccessI16_L(PyAccess):
+ """ I;16L access, with conversion """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return pixel.l + pixel.r * 256
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ try:
+ color = min(color, 65535)
+ except:
+ color = min(color[0], 65535)
+
+ pixel.l = color & 0xFF
+ pixel.r = color >> 8
+
+
+class _PyAccessI16_B(PyAccess):
+ """ I;16B access, with conversion """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast('struct Pixel_I16 **', self.image)
+
+ def get_pixel(self, x, y):
+ pixel = self.pixels[y][x]
+ return pixel.l * 256 + pixel.r
+
+ def set_pixel(self, x, y, color):
+ pixel = self.pixels[y][x]
+ try:
+ color = min(color, 65535)
+ except:
+ color = min(color[0], 65535)
+
+ pixel.l = color >> 8
+ pixel.r = color & 0xFF
+
+
+class _PyAccessI32_N(PyAccess):
+ """ Signed Int32 access, native endian """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = self.image32
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ self.pixels[y][x] = color
+
+
+class _PyAccessI32_Swap(PyAccess):
+ """ I;32L/B access, with byteswapping conversion """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = self.image32
+
+ def reverse(self, i):
+ orig = ffi.new('int *', i)
+ chars = ffi.cast('unsigned char *', orig)
+ chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], \
+ chars[1], chars[0]
+ return ffi.cast('int *', chars)[0]
+
+ def get_pixel(self, x, y):
+ return self.reverse(self.pixels[y][x])
+
+ def set_pixel(self, x, y, color):
+ self.pixels[y][x] = self.reverse(color)
+
+
+class _PyAccessF(PyAccess):
+ """ 32 bit float access """
+ def _post_init(self, *args, **kwargs):
+ self.pixels = ffi.cast('float **', self.image32)
+
+ def get_pixel(self, x, y):
+ return self.pixels[y][x]
+
+ def set_pixel(self, x, y, color):
+ try:
+ # not a tuple
+ self.pixels[y][x] = color
+ except:
+ # tuple
+ self.pixels[y][x] = color[0]
+
+
+mode_map = {'1': _PyAccess8,
+ 'L': _PyAccess8,
+ 'P': _PyAccess8,
+ 'LA': _PyAccess32_2,
+ 'PA': _PyAccess32_2,
+ 'RGB': _PyAccess32_3,
+ 'LAB': _PyAccess32_3,
+ 'HSV': _PyAccess32_3,
+ 'YCbCr': _PyAccess32_3,
+ 'RGBA': _PyAccess32_4,
+ 'RGBa': _PyAccess32_4,
+ 'RGBX': _PyAccess32_4,
+ 'CMYK': _PyAccess32_4,
+ 'F': _PyAccessF,
+ 'I': _PyAccessI32_N,
+ }
+
+if sys.byteorder == 'little':
+ mode_map['I;16'] = _PyAccessI16_N
+ mode_map['I;16L'] = _PyAccessI16_N
+ mode_map['I;16B'] = _PyAccessI16_B
+
+ mode_map['I;32L'] = _PyAccessI32_N
+ mode_map['I;32B'] = _PyAccessI32_Swap
+else:
+ mode_map['I;16'] = _PyAccessI16_L
+ mode_map['I;16L'] = _PyAccessI16_L
+ mode_map['I;16B'] = _PyAccessI16_N
+
+ mode_map['I;32L'] = _PyAccessI32_Swap
+ mode_map['I;32B'] = _PyAccessI32_N
+
+
+def new(img, readonly=False):
+ access_type = mode_map.get(img.mode, None)
+ if not access_type:
+ if DEBUG:
+ print("PyAccess Not Implemented: %s" % img.mode)
+ return None
+ if DEBUG:
+ print("New PyAccess: %s" % img.mode)
+ return access_type(img, readonly)
+
+# End of file
diff --git a/lib/Python/Lib/PIL/SgiImagePlugin.py b/lib/Python/Lib/PIL/SgiImagePlugin.py
new file mode 100644
index 000000000..2b8fcd8e4
--- /dev/null
+++ b/lib/Python/Lib/PIL/SgiImagePlugin.py
@@ -0,0 +1,91 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# SGI image file handling
+#
+# See "The SGI Image File Format (Draft version 0.97)", Paul Haeberli.
+# <ftp://ftp.sgi.com/graphics/SGIIMAGESPEC>
+#
+# History:
+# 1995-09-10 fl Created
+#
+# Copyright (c) 2008 by Karsten Hiddemann.
+# Copyright (c) 1997 by Secret Labs AB.
+# Copyright (c) 1995 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+
+from PIL import Image, ImageFile, _binary
+
+i8 = _binary.i8
+i16 = _binary.i16be
+i32 = _binary.i32be
+
+
+def _accept(prefix):
+ return i16(prefix) == 474
+
+
+##
+# Image plugin for SGI images.
+
+class SgiImageFile(ImageFile.ImageFile):
+
+ format = "SGI"
+ format_description = "SGI Image File Format"
+
+ def _open(self):
+
+ # HEAD
+ s = self.fp.read(512)
+ if i16(s) != 474:
+ raise ValueError("Not an SGI image file")
+
+ # relevant header entries
+ compression = i8(s[2])
+
+ # bytes, dimension, zsize
+ layout = i8(s[3]), i16(s[4:]), i16(s[10:])
+
+ # determine mode from bytes/zsize
+ if layout == (1, 2, 1) or layout == (1, 1, 1):
+ self.mode = "L"
+ elif layout == (1, 3, 3):
+ self.mode = "RGB"
+ elif layout == (1, 3, 4):
+ self.mode = "RGBA"
+ else:
+ raise ValueError("Unsupported SGI image mode")
+
+ # size
+ self.size = i16(s[6:]), i16(s[8:])
+
+ # decoder info
+ if compression == 0:
+ offset = 512
+ pagesize = self.size[0]*self.size[1]*layout[0]
+ self.tile = []
+ for layer in self.mode:
+ self.tile.append(
+ ("raw", (0, 0)+self.size, offset, (layer, 0, -1)))
+ offset = offset + pagesize
+ elif compression == 1:
+ raise ValueError("SGI RLE encoding not supported")
+
+#
+# registry
+
+Image.register_open("SGI", SgiImageFile, _accept)
+
+Image.register_extension("SGI", ".bw")
+Image.register_extension("SGI", ".rgb")
+Image.register_extension("SGI", ".rgba")
+Image.register_extension("SGI", ".sgi")
+
+# End of file
diff --git a/lib/Python/Lib/PIL/SpiderImagePlugin.py b/lib/Python/Lib/PIL/SpiderImagePlugin.py
new file mode 100644
index 000000000..306b348bc
--- /dev/null
+++ b/lib/Python/Lib/PIL/SpiderImagePlugin.py
@@ -0,0 +1,312 @@
+#
+# The Python Imaging Library.
+#
+# SPIDER image file handling
+#
+# History:
+# 2004-08-02 Created BB
+# 2006-03-02 added save method
+# 2006-03-13 added support for stack images
+#
+# Copyright (c) 2004 by Health Research Inc. (HRI) RENSSELAER, NY 12144.
+# Copyright (c) 2004 by William Baxter.
+# Copyright (c) 2004 by Secret Labs AB.
+# Copyright (c) 2004 by Fredrik Lundh.
+#
+
+##
+# Image plugin for the Spider image format. This format is is used
+# by the SPIDER software, in processing image data from electron
+# microscopy and tomography.
+##
+
+#
+# SpiderImagePlugin.py
+#
+# The Spider image format is used by SPIDER software, in processing
+# image data from electron microscopy and tomography.
+#
+# Spider home page:
+# http://www.wadsworth.org/spider_doc/spider/docs/spider.html
+#
+# Details about the Spider image format:
+# http://www.wadsworth.org/spider_doc/spider/docs/image_doc.html
+#
+
+from __future__ import print_function
+
+from PIL import Image, ImageFile
+import os
+import struct
+import sys
+
+
+def isInt(f):
+ try:
+ i = int(f)
+ if f-i == 0:
+ return 1
+ else:
+ return 0
+ except:
+ return 0
+
+iforms = [1, 3, -11, -12, -21, -22]
+
+
+# There is no magic number to identify Spider files, so just check a
+# series of header locations to see if they have reasonable values.
+# Returns no.of bytes in the header, if it is a valid Spider header,
+# otherwise returns 0
+
+def isSpiderHeader(t):
+ h = (99,) + t # add 1 value so can use spider header index start=1
+ # header values 1,2,5,12,13,22,23 should be integers
+ for i in [1, 2, 5, 12, 13, 22, 23]:
+ if not isInt(h[i]):
+ return 0
+ # check iform
+ iform = int(h[5])
+ if iform not in iforms:
+ return 0
+ # check other header values
+ labrec = int(h[13]) # no. records in file header
+ labbyt = int(h[22]) # total no. of bytes in header
+ lenbyt = int(h[23]) # record length in bytes
+ # print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)
+ if labbyt != (labrec * lenbyt):
+ return 0
+ # looks like a valid header
+ return labbyt
+
+
+def isSpiderImage(filename):
+ fp = open(filename, 'rb')
+ f = fp.read(92) # read 23 * 4 bytes
+ fp.close()
+ t = struct.unpack('>23f', f) # try big-endian first
+ hdrlen = isSpiderHeader(t)
+ if hdrlen == 0:
+ t = struct.unpack('<23f', f) # little-endian
+ hdrlen = isSpiderHeader(t)
+ return hdrlen
+
+
+class SpiderImageFile(ImageFile.ImageFile):
+
+ format = "SPIDER"
+ format_description = "Spider 2D image"
+
+ def _open(self):
+ # check header
+ n = 27 * 4 # read 27 float values
+ f = self.fp.read(n)
+
+ try:
+ self.bigendian = 1
+ t = struct.unpack('>27f', f) # try big-endian first
+ hdrlen = isSpiderHeader(t)
+ if hdrlen == 0:
+ self.bigendian = 0
+ t = struct.unpack('<27f', f) # little-endian
+ hdrlen = isSpiderHeader(t)
+ if hdrlen == 0:
+ raise SyntaxError("not a valid Spider file")
+ except struct.error:
+ raise SyntaxError("not a valid Spider file")
+
+ h = (99,) + t # add 1 value : spider header index starts at 1
+ iform = int(h[5])
+ if iform != 1:
+ raise SyntaxError("not a Spider 2D image")
+
+ self.size = int(h[12]), int(h[2]) # size in pixels (width, height)
+ self.istack = int(h[24])
+ self.imgnumber = int(h[27])
+
+ if self.istack == 0 and self.imgnumber == 0:
+ # stk=0, img=0: a regular 2D image
+ offset = hdrlen
+ self.nimages = 1
+ elif self.istack > 0 and self.imgnumber == 0:
+ # stk>0, img=0: Opening the stack for the first time
+ self.imgbytes = int(h[12]) * int(h[2]) * 4
+ self.hdrlen = hdrlen
+ self.nimages = int(h[26])
+ # Point to the first image in the stack
+ offset = hdrlen * 2
+ self.imgnumber = 1
+ elif self.istack == 0 and self.imgnumber > 0:
+ # stk=0, img>0: an image within the stack
+ offset = hdrlen + self.stkoffset
+ self.istack = 2 # So Image knows it's still a stack
+ else:
+ raise SyntaxError("inconsistent stack header values")
+
+ if self.bigendian:
+ self.rawmode = "F;32BF"
+ else:
+ self.rawmode = "F;32F"
+ self.mode = "F"
+
+ self.tile = [
+ ("raw", (0, 0) + self.size, offset,
+ (self.rawmode, 0, 1))]
+ self.__fp = self.fp # FIXME: hack
+
+ # 1st image index is zero (although SPIDER imgnumber starts at 1)
+ def tell(self):
+ if self.imgnumber < 1:
+ return 0
+ else:
+ return self.imgnumber - 1
+
+ def seek(self, frame):
+ if self.istack == 0:
+ return
+ if frame >= self.nimages:
+ raise EOFError("attempt to seek past end of file")
+ self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
+ self.fp = self.__fp
+ self.fp.seek(self.stkoffset)
+ self._open()
+
+ # returns a byte image after rescaling to 0..255
+ def convert2byte(self, depth=255):
+ (min, max) = self.getextrema()
+ m = 1
+ if max != min:
+ m = depth / (max-min)
+ b = -m * min
+ return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
+
+ # returns a ImageTk.PhotoImage object, after rescaling to 0..255
+ def tkPhotoImage(self):
+ from PIL import ImageTk
+ return ImageTk.PhotoImage(self.convert2byte(), palette=256)
+
+
+# --------------------------------------------------------------------
+# Image series
+
+# given a list of filenames, return a list of images
+def loadImageSeries(filelist=None):
+ " create a list of Image.images for use in montage "
+ if filelist is None or len(filelist) < 1:
+ return
+
+ imglist = []
+ for img in filelist:
+ if not os.path.exists(img):
+ print("unable to find %s" % img)
+ continue
+ try:
+ im = Image.open(img).convert2byte()
+ except:
+ if not isSpiderImage(img):
+ print(img + " is not a Spider image file")
+ continue
+ im.info['filename'] = img
+ imglist.append(im)
+ return imglist
+
+
+# --------------------------------------------------------------------
+# For saving images in Spider format
+
+def makeSpiderHeader(im):
+ nsam, nrow = im.size
+ lenbyt = nsam * 4 # There are labrec records in the header
+ labrec = 1024 / lenbyt
+ if 1024 % lenbyt != 0:
+ labrec += 1
+ labbyt = labrec * lenbyt
+ hdr = []
+ nvalues = int(labbyt / 4)
+ for i in range(nvalues):
+ hdr.append(0.0)
+
+ if len(hdr) < 23:
+ return []
+
+ # NB these are Fortran indices
+ hdr[1] = 1.0 # nslice (=1 for an image)
+ hdr[2] = float(nrow) # number of rows per slice
+ hdr[5] = 1.0 # iform for 2D image
+ hdr[12] = float(nsam) # number of pixels per line
+ hdr[13] = float(labrec) # number of records in file header
+ hdr[22] = float(labbyt) # total number of bytes in header
+ hdr[23] = float(lenbyt) # record length in bytes
+
+ # adjust for Fortran indexing
+ hdr = hdr[1:]
+ hdr.append(0.0)
+ # pack binary data into a string
+ hdrstr = []
+ for v in hdr:
+ hdrstr.append(struct.pack('f', v))
+ return hdrstr
+
+
+def _save(im, fp, filename):
+ if im.mode[0] != "F":
+ im = im.convert('F')
+
+ hdr = makeSpiderHeader(im)
+ if len(hdr) < 256:
+ raise IOError("Error creating Spider header")
+
+ # write the SPIDER header
+ try:
+ fp = open(filename, 'wb')
+ except:
+ raise IOError("Unable to open %s for writing" % filename)
+ fp.writelines(hdr)
+
+ rawmode = "F;32NF" # 32-bit native floating point
+ ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
+
+ fp.close()
+
+
+def _save_spider(im, fp, filename):
+ # get the filename extension and register it with Image
+ fn, ext = os.path.splitext(filename)
+ Image.register_extension("SPIDER", ext)
+ _save(im, fp, filename)
+
+# --------------------------------------------------------------------
+
+Image.register_open("SPIDER", SpiderImageFile)
+Image.register_save("SPIDER", _save_spider)
+
+if __name__ == "__main__":
+
+ if not sys.argv[1:]:
+ print("Syntax: python SpiderImagePlugin.py Spiderimage [outfile]")
+ sys.exit()
+
+ filename = sys.argv[1]
+ if not isSpiderImage(filename):
+ print("input image must be in Spider format")
+ sys.exit()
+
+ outfile = ""
+ if len(sys.argv[1:]) > 1:
+ outfile = sys.argv[2]
+
+ im = Image.open(filename)
+ print("image: " + str(im))
+ print("format: " + str(im.format))
+ print("size: " + str(im.size))
+ print("mode: " + str(im.mode))
+ print("max, min: ", end=' ')
+ print(im.getextrema())
+
+ if outfile != "":
+ # perform some image operation
+ im = im.transpose(Image.FLIP_LEFT_RIGHT)
+ print(
+ "saving a flipped version of %s as %s " %
+ (os.path.basename(filename), outfile))
+ im.save(outfile, "SPIDER")
diff --git a/lib/Python/Lib/PIL/SunImagePlugin.py b/lib/Python/Lib/PIL/SunImagePlugin.py
new file mode 100644
index 000000000..e0a7aa6ee
--- /dev/null
+++ b/lib/Python/Lib/PIL/SunImagePlugin.py
@@ -0,0 +1,83 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Sun image file handling
+#
+# History:
+# 1995-09-10 fl Created
+# 1996-05-28 fl Fixed 32-bit alignment
+# 1998-12-29 fl Import ImagePalette module
+# 2001-12-18 fl Fixed palette loading (from Jean-Claude Rimbault)
+#
+# Copyright (c) 1997-2001 by Secret Labs AB
+# Copyright (c) 1995-1996 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.3"
+
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+
+i16 = _binary.i16be
+i32 = _binary.i32be
+
+
+def _accept(prefix):
+ return i32(prefix) == 0x59a66a95
+
+
+##
+# Image plugin for Sun raster files.
+
+class SunImageFile(ImageFile.ImageFile):
+
+ format = "SUN"
+ format_description = "Sun Raster File"
+
+ def _open(self):
+
+ # HEAD
+ s = self.fp.read(32)
+ if i32(s) != 0x59a66a95:
+ raise SyntaxError("not an SUN raster file")
+
+ offset = 32
+
+ self.size = i32(s[4:8]), i32(s[8:12])
+
+ depth = i32(s[12:16])
+ if depth == 1:
+ self.mode, rawmode = "1", "1;I"
+ elif depth == 8:
+ self.mode = rawmode = "L"
+ elif depth == 24:
+ self.mode, rawmode = "RGB", "BGR"
+ else:
+ raise SyntaxError("unsupported mode")
+
+ compression = i32(s[20:24])
+
+ if i32(s[24:28]) != 0:
+ length = i32(s[28:32])
+ offset = offset + length
+ self.palette = ImagePalette.raw("RGB;L", self.fp.read(length))
+ if self.mode == "L":
+ self.mode = rawmode = "P"
+
+ stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3)
+
+ if compression == 1:
+ self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))]
+ elif compression == 2:
+ self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)]
+
+#
+# registry
+
+Image.register_open("SUN", SunImageFile, _accept)
+
+Image.register_extension("SUN", ".ras")
diff --git a/lib/Python/Lib/PIL/TarIO.py b/lib/Python/Lib/PIL/TarIO.py
new file mode 100644
index 000000000..4e5115b26
--- /dev/null
+++ b/lib/Python/Lib/PIL/TarIO.py
@@ -0,0 +1,57 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# read files from within a tar file
+#
+# History:
+# 95-06-18 fl Created
+# 96-05-28 fl Open files in binary mode
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1995-96.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from PIL import ContainerIO
+
+
+##
+# A file object that provides read access to a given member of a TAR
+# file.
+
+class TarIO(ContainerIO.ContainerIO):
+
+ ##
+ # Create file object.
+ #
+ # @param tarfile Name of TAR file.
+ # @param file Name of member file.
+
+ def __init__(self, tarfile, file):
+
+ fh = open(tarfile, "rb")
+
+ while True:
+
+ s = fh.read(512)
+ if len(s) != 512:
+ raise IOError("unexpected end of tar file")
+
+ name = s[:100].decode('utf-8')
+ i = name.find('\0')
+ if i == 0:
+ raise IOError("cannot find subfile")
+ if i > 0:
+ name = name[:i]
+
+ size = int(s[124:135], 8)
+
+ if file == name:
+ break
+
+ fh.seek((size + 511) & (~511), 1)
+
+ # Open region
+ ContainerIO.ContainerIO.__init__(self, fh, fh.tell(), size)
diff --git a/lib/Python/Lib/PIL/TgaImagePlugin.py b/lib/Python/Lib/PIL/TgaImagePlugin.py
new file mode 100644
index 000000000..46eafe8d0
--- /dev/null
+++ b/lib/Python/Lib/PIL/TgaImagePlugin.py
@@ -0,0 +1,199 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# TGA file handling
+#
+# History:
+# 95-09-01 fl created (reads 24-bit files only)
+# 97-01-04 fl support more TGA versions, including compressed images
+# 98-07-04 fl fixed orientation and alpha layer bugs
+# 98-09-11 fl fixed orientation for runlength decoder
+#
+# Copyright (c) Secret Labs AB 1997-98.
+# Copyright (c) Fredrik Lundh 1995-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.3"
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+
+
+#
+# --------------------------------------------------------------------
+# Read RGA file
+
+i8 = _binary.i8
+i16 = _binary.i16le
+i32 = _binary.i32le
+
+
+MODES = {
+ # map imagetype/depth to rawmode
+ (1, 8): "P",
+ (3, 1): "1",
+ (3, 8): "L",
+ (2, 16): "BGR;5",
+ (2, 24): "BGR",
+ (2, 32): "BGRA",
+}
+
+
+##
+# Image plugin for Targa files.
+
+class TgaImageFile(ImageFile.ImageFile):
+
+ format = "TGA"
+ format_description = "Targa"
+
+ def _open(self):
+
+ # process header
+ s = self.fp.read(18)
+
+ idlen = i8(s[0])
+
+ colormaptype = i8(s[1])
+ imagetype = i8(s[2])
+
+ depth = i8(s[16])
+
+ flags = i8(s[17])
+
+ self.size = i16(s[12:]), i16(s[14:])
+
+ # validate header fields
+ if colormaptype not in (0, 1) or\
+ self.size[0] <= 0 or self.size[1] <= 0 or\
+ depth not in (1, 8, 16, 24, 32):
+ raise SyntaxError("not a TGA file")
+
+ # image mode
+ if imagetype in (3, 11):
+ self.mode = "L"
+ if depth == 1:
+ self.mode = "1" # ???
+ elif imagetype in (1, 9):
+ self.mode = "P"
+ elif imagetype in (2, 10):
+ self.mode = "RGB"
+ if depth == 32:
+ self.mode = "RGBA"
+ else:
+ raise SyntaxError("unknown TGA mode")
+
+ # orientation
+ orientation = flags & 0x30
+ if orientation == 0x20:
+ orientation = 1
+ elif not orientation:
+ orientation = -1
+ else:
+ raise SyntaxError("unknown TGA orientation")
+
+ self.info["orientation"] = orientation
+
+ if imagetype & 8:
+ self.info["compression"] = "tga_rle"
+
+ if idlen:
+ self.info["id_section"] = self.fp.read(idlen)
+
+ if colormaptype:
+ # read palette
+ start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:])
+ if mapdepth == 16:
+ self.palette = ImagePalette.raw(
+ "BGR;16", b"\0"*2*start + self.fp.read(2*size))
+ elif mapdepth == 24:
+ self.palette = ImagePalette.raw(
+ "BGR", b"\0"*3*start + self.fp.read(3*size))
+ elif mapdepth == 32:
+ self.palette = ImagePalette.raw(
+ "BGRA", b"\0"*4*start + self.fp.read(4*size))
+
+ # setup tile descriptor
+ try:
+ rawmode = MODES[(imagetype & 7, depth)]
+ if imagetype & 8:
+ # compressed
+ self.tile = [("tga_rle", (0, 0)+self.size,
+ self.fp.tell(), (rawmode, orientation, depth))]
+ else:
+ self.tile = [("raw", (0, 0)+self.size,
+ self.fp.tell(), (rawmode, 0, orientation))]
+ except KeyError:
+ pass # cannot decode
+
+#
+# --------------------------------------------------------------------
+# Write TGA file
+
+o8 = _binary.o8
+o16 = _binary.o16le
+o32 = _binary.o32le
+
+SAVE = {
+ "1": ("1", 1, 0, 3),
+ "L": ("L", 8, 0, 3),
+ "P": ("P", 8, 1, 1),
+ "RGB": ("BGR", 24, 0, 2),
+ "RGBA": ("BGRA", 32, 0, 2),
+}
+
+
+def _save(im, fp, filename, check=0):
+
+ try:
+ rawmode, bits, colormaptype, imagetype = SAVE[im.mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as TGA" % im.mode)
+
+ if check:
+ return check
+
+ if colormaptype:
+ colormapfirst, colormaplength, colormapentry = 0, 256, 24
+ else:
+ colormapfirst, colormaplength, colormapentry = 0, 0, 0
+
+ if im.mode == "RGBA":
+ flags = 8
+ else:
+ flags = 0
+
+ orientation = im.info.get("orientation", -1)
+ if orientation > 0:
+ flags = flags | 0x20
+
+ fp.write(b"\000" +
+ o8(colormaptype) +
+ o8(imagetype) +
+ o16(colormapfirst) +
+ o16(colormaplength) +
+ o8(colormapentry) +
+ o16(0) +
+ o16(0) +
+ o16(im.size[0]) +
+ o16(im.size[1]) +
+ o8(bits) +
+ o8(flags))
+
+ if colormaptype:
+ fp.write(im.im.getpalette("RGB", "BGR"))
+
+ ImageFile._save(
+ im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))])
+
+#
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open("TGA", TgaImageFile)
+Image.register_save("TGA", _save)
+
+Image.register_extension("TGA", ".tga")
diff --git a/lib/Python/Lib/PIL/TiffImagePlugin.py b/lib/Python/Lib/PIL/TiffImagePlugin.py
new file mode 100644
index 000000000..a533c27ea
--- /dev/null
+++ b/lib/Python/Lib/PIL/TiffImagePlugin.py
@@ -0,0 +1,1225 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# TIFF file handling
+#
+# TIFF is a flexible, if somewhat aged, image file format originally
+# defined by Aldus. Although TIFF supports a wide variety of pixel
+# layouts and compression methods, the name doesn't really stand for
+# "thousands of incompatible file formats," it just feels that way.
+#
+# To read TIFF data from a stream, the stream must be seekable. For
+# progressive decoding, make sure to use TIFF files where the tag
+# directory is placed first in the file.
+#
+# History:
+# 1995-09-01 fl Created
+# 1996-05-04 fl Handle JPEGTABLES tag
+# 1996-05-18 fl Fixed COLORMAP support
+# 1997-01-05 fl Fixed PREDICTOR support
+# 1997-08-27 fl Added support for rational tags (from Perry Stoll)
+# 1998-01-10 fl Fixed seek/tell (from Jan Blom)
+# 1998-07-15 fl Use private names for internal variables
+# 1999-06-13 fl Rewritten for PIL 1.0 (1.0)
+# 2000-10-11 fl Additional fixes for Python 2.0 (1.1)
+# 2001-04-17 fl Fixed rewind support (seek to frame 0) (1.2)
+# 2001-05-12 fl Added write support for more tags (from Greg Couch) (1.3)
+# 2001-12-18 fl Added workaround for broken Matrox library
+# 2002-01-18 fl Don't mess up if photometric tag is missing (D. Alan Stewart)
+# 2003-05-19 fl Check FILLORDER tag
+# 2003-09-26 fl Added RGBa support
+# 2004-02-24 fl Added DPI support; fixed rational write support
+# 2005-02-07 fl Added workaround for broken Corel Draw 10 files
+# 2006-01-09 fl Added support for float/double tags (from Russell Nelson)
+#
+# Copyright (c) 1997-2006 by Secret Labs AB. All rights reserved.
+# Copyright (c) 1995-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from __future__ import print_function
+
+__version__ = "1.3.5"
+
+from PIL import Image, ImageFile
+from PIL import ImagePalette
+from PIL import _binary
+from PIL._util import isStringType
+
+import warnings
+import array
+import sys
+import collections
+import itertools
+import os
+import io
+
+# Set these to true to force use of libtiff for reading or writing.
+READ_LIBTIFF = False
+WRITE_LIBTIFF = False
+
+II = b"II" # little-endian (Intel style)
+MM = b"MM" # big-endian (Motorola style)
+
+i8 = _binary.i8
+o8 = _binary.o8
+
+if sys.byteorder == "little":
+ native_prefix = II
+else:
+ native_prefix = MM
+
+#
+# --------------------------------------------------------------------
+# Read TIFF files
+
+il16 = _binary.i16le
+il32 = _binary.i32le
+ol16 = _binary.o16le
+ol32 = _binary.o32le
+
+ib16 = _binary.i16be
+ib32 = _binary.i32be
+ob16 = _binary.o16be
+ob32 = _binary.o32be
+
+# a few tag names, just to make the code below a bit more readable
+IMAGEWIDTH = 256
+IMAGELENGTH = 257
+BITSPERSAMPLE = 258
+COMPRESSION = 259
+PHOTOMETRIC_INTERPRETATION = 262
+FILLORDER = 266
+IMAGEDESCRIPTION = 270
+STRIPOFFSETS = 273
+SAMPLESPERPIXEL = 277
+ROWSPERSTRIP = 278
+STRIPBYTECOUNTS = 279
+X_RESOLUTION = 282
+Y_RESOLUTION = 283
+PLANAR_CONFIGURATION = 284
+RESOLUTION_UNIT = 296
+SOFTWARE = 305
+DATE_TIME = 306
+ARTIST = 315
+PREDICTOR = 317
+COLORMAP = 320
+TILEOFFSETS = 324
+EXTRASAMPLES = 338
+SAMPLEFORMAT = 339
+JPEGTABLES = 347
+COPYRIGHT = 33432
+IPTC_NAA_CHUNK = 33723 # newsphoto properties
+PHOTOSHOP_CHUNK = 34377 # photoshop properties
+ICCPROFILE = 34675
+EXIFIFD = 34665
+XMP = 700
+
+# https://github.com/fiji/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java
+IMAGEJ_META_DATA_BYTE_COUNTS = 50838
+IMAGEJ_META_DATA = 50839
+
+COMPRESSION_INFO = {
+ # Compression => pil compression name
+ 1: "raw",
+ 2: "tiff_ccitt",
+ 3: "group3",
+ 4: "group4",
+ 5: "tiff_lzw",
+ 6: "tiff_jpeg", # obsolete
+ 7: "jpeg",
+ 8: "tiff_adobe_deflate",
+ 32771: "tiff_raw_16", # 16-bit padding
+ 32773: "packbits",
+ 32809: "tiff_thunderscan",
+ 32946: "tiff_deflate",
+ 34676: "tiff_sgilog",
+ 34677: "tiff_sgilog24",
+}
+
+COMPRESSION_INFO_REV = dict([(v, k) for (k, v) in COMPRESSION_INFO.items()])
+
+OPEN_INFO = {
+ # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
+ # ExtraSamples) => mode, rawmode
+ (II, 0, 1, 1, (1,), ()): ("1", "1;I"),
+ (II, 0, 1, 2, (1,), ()): ("1", "1;IR"),
+ (II, 0, 1, 1, (8,), ()): ("L", "L;I"),
+ (II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
+ (II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
+ (II, 1, 1, 1, (1,), ()): ("1", "1"),
+ (II, 1, 1, 1, (4,), ()): ("L", "L;4"),
+ (II, 1, 1, 2, (1,), ()): ("1", "1;R"),
+ (II, 1, 1, 1, (8,), ()): ("L", "L"),
+ (II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
+ (II, 1, 1, 2, (8,), ()): ("L", "L;R"),
+ (II, 1, 1, 1, (12,), ()): ("I;16", "I;12"),
+ (II, 1, 1, 1, (16,), ()): ("I;16", "I;16"),
+ (II, 1, 2, 1, (16,), ()): ("I;16S", "I;16S"),
+ (II, 1, 1, 1, (32,), ()): ("I", "I;32N"),
+ (II, 1, 2, 1, (32,), ()): ("I", "I;32S"),
+ (II, 1, 3, 1, (32,), ()): ("F", "F;32F"),
+ (II, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
+ (II, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
+ (II, 2, 1, 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
+ (II, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
+ (II, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
+ (II, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
+ (II, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
+ (II, 3, 1, 1, (1,), ()): ("P", "P;1"),
+ (II, 3, 1, 2, (1,), ()): ("P", "P;1R"),
+ (II, 3, 1, 1, (2,), ()): ("P", "P;2"),
+ (II, 3, 1, 2, (2,), ()): ("P", "P;2R"),
+ (II, 3, 1, 1, (4,), ()): ("P", "P;4"),
+ (II, 3, 1, 2, (4,), ()): ("P", "P;4R"),
+ (II, 3, 1, 1, (8,), ()): ("P", "P"),
+ (II, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"),
+ (II, 3, 1, 2, (8,), ()): ("P", "P;R"),
+ (II, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
+ (II, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
+ (II, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
+
+ (MM, 0, 1, 1, (1,), ()): ("1", "1;I"),
+ (MM, 0, 1, 2, (1,), ()): ("1", "1;IR"),
+ (MM, 0, 1, 1, (8,), ()): ("L", "L;I"),
+ (MM, 0, 1, 2, (8,), ()): ("L", "L;IR"),
+ (MM, 1, 1, 1, (1,), ()): ("1", "1"),
+ (MM, 1, 1, 2, (1,), ()): ("1", "1;R"),
+ (MM, 1, 1, 1, (8,), ()): ("L", "L"),
+ (MM, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"),
+ (MM, 1, 1, 2, (8,), ()): ("L", "L;R"),
+ (MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"),
+ (MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"),
+ (MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"),
+ (MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"),
+ (MM, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"),
+ (MM, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
+ (MM, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
+ (MM, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
+ (MM, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
+ (MM, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
+ (MM, 3, 1, 1, (1,), ()): ("P", "P;1"),
+ (MM, 3, 1, 2, (1,), ()): ("P", "P;1R"),
+ (MM, 3, 1, 1, (2,), ()): ("P", "P;2"),
+ (MM, 3, 1, 2, (2,), ()): ("P", "P;2R"),
+ (MM, 3, 1, 1, (4,), ()): ("P", "P;4"),
+ (MM, 3, 1, 2, (4,), ()): ("P", "P;4R"),
+ (MM, 3, 1, 1, (8,), ()): ("P", "P"),
+ (MM, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"),
+ (MM, 3, 1, 2, (8,), ()): ("P", "P;R"),
+ (MM, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
+ (MM, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"),
+ (MM, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"),
+
+}
+
+PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"]
+
+
+def _accept(prefix):
+ return prefix[:4] in PREFIXES
+
+
+##
+# Wrapper for TIFF IFDs.
+
+class ImageFileDirectory(collections.MutableMapping):
+ """ This class represents a TIFF tag directory. To speed things
+ up, we don't decode tags unless they're asked for.
+
+ Exposes a dictionary interface of the tags in the directory
+ ImageFileDirectory[key] = value
+ value = ImageFileDirectory[key]
+
+ Also contains a dictionary of tag types as read from the tiff
+ image file, 'ImageFileDirectory.tagtype'
+
+
+ Data Structures:
+ 'public'
+ * self.tagtype = {} Key: numerical tiff tag number
+ Value: integer corresponding to the data type from
+ `TiffTags.TYPES`
+
+ 'internal'
+ * self.tags = {} Key: numerical tiff tag number
+ Value: Decoded data, Generally a tuple.
+ * If set from __setval__ -- always a tuple
+ * Numeric types -- always a tuple
+ * String type -- not a tuple, returned as string
+ * Undefined data -- not a tuple, returned as bytes
+ * Byte -- not a tuple, returned as byte.
+ * self.tagdata = {} Key: numerical tiff tag number
+ Value: undecoded byte string from file
+
+
+ Tags will be found in either self.tags or self.tagdata, but
+ not both. The union of the two should contain all the tags
+ from the Tiff image file. External classes shouldn't
+ reference these unless they're really sure what they're doing.
+ """
+
+ def __init__(self, prefix=II):
+ """
+ :prefix: 'II'|'MM' tiff endianness
+ """
+ self.prefix = prefix[:2]
+ if self.prefix == MM:
+ self.i16, self.i32 = ib16, ib32
+ self.o16, self.o32 = ob16, ob32
+ elif self.prefix == II:
+ self.i16, self.i32 = il16, il32
+ self.o16, self.o32 = ol16, ol32
+ else:
+ raise SyntaxError("not a TIFF IFD")
+ self.reset()
+
+ def reset(self):
+ #: Tags is an incomplete dictionary of the tags of the image.
+ #: For a complete dictionary, use the as_dict method.
+ self.tags = {}
+ self.tagdata = {}
+ self.tagtype = {} # added 2008-06-05 by Florian Hoech
+ self.next = None
+ self.offset = None
+
+ def __str__(self):
+ return str(self.as_dict())
+
+ def as_dict(self):
+ """Return a dictionary of the image's tags."""
+ return dict(self.items())
+
+ def named(self):
+ """
+ Returns the complete tag dictionary, with named tags where posible.
+ """
+ from PIL import TiffTags
+ result = {}
+ for tag_code, value in self.items():
+ tag_name = TiffTags.TAGS.get(tag_code, tag_code)
+ result[tag_name] = value
+ return result
+
+ # dictionary API
+
+ def __len__(self):
+ return len(self.tagdata) + len(self.tags)
+
+ def __getitem__(self, tag):
+ try:
+ return self.tags[tag]
+ except KeyError:
+ data = self.tagdata[tag] # unpack on the fly
+ type = self.tagtype[tag]
+ size, handler = self.load_dispatch[type]
+ self.tags[tag] = data = handler(self, data)
+ del self.tagdata[tag]
+ return data
+
+ def getscalar(self, tag, default=None):
+ try:
+ value = self[tag]
+ if len(value) != 1:
+ if tag == SAMPLEFORMAT:
+ # work around broken (?) matrox library
+ # (from Ted Wright, via Bob Klimek)
+ raise KeyError # use default
+ raise ValueError("not a scalar")
+ return value[0]
+ except KeyError:
+ if default is None:
+ raise
+ return default
+
+ def __contains__(self, tag):
+ return tag in self.tags or tag in self.tagdata
+
+ if bytes is str:
+ def has_key(self, tag):
+ return tag in self
+
+ def __setitem__(self, tag, value):
+ # tags are tuples for integers
+ # tags are not tuples for byte, string, and undefined data.
+ # see load_*
+ if not isinstance(value, tuple):
+ value = (value,)
+ self.tags[tag] = value
+
+ def __delitem__(self, tag):
+ self.tags.pop(tag, self.tagdata.pop(tag, None))
+
+ def __iter__(self):
+ return itertools.chain(self.tags.__iter__(), self.tagdata.__iter__())
+
+ def items(self):
+ keys = list(self.__iter__())
+ values = [self[key] for key in keys]
+ return zip(keys, values)
+
+ # load primitives
+
+ load_dispatch = {}
+
+ def load_byte(self, data):
+ return data
+ load_dispatch[1] = (1, load_byte)
+
+ def load_string(self, data):
+ if data[-1:] == b'\0':
+ data = data[:-1]
+ return data.decode('latin-1', 'replace')
+ load_dispatch[2] = (1, load_string)
+
+ def load_short(self, data):
+ l = []
+ for i in range(0, len(data), 2):
+ l.append(self.i16(data, i))
+ return tuple(l)
+ load_dispatch[3] = (2, load_short)
+
+ def load_long(self, data):
+ l = []
+ for i in range(0, len(data), 4):
+ l.append(self.i32(data, i))
+ return tuple(l)
+ load_dispatch[4] = (4, load_long)
+
+ def load_rational(self, data):
+ l = []
+ for i in range(0, len(data), 8):
+ l.append((self.i32(data, i), self.i32(data, i+4)))
+ return tuple(l)
+ load_dispatch[5] = (8, load_rational)
+
+ def load_float(self, data):
+ a = array.array("f", data)
+ if self.prefix != native_prefix:
+ a.byteswap()
+ return tuple(a)
+ load_dispatch[11] = (4, load_float)
+
+ def load_double(self, data):
+ a = array.array("d", data)
+ if self.prefix != native_prefix:
+ a.byteswap()
+ return tuple(a)
+ load_dispatch[12] = (8, load_double)
+
+ def load_undefined(self, data):
+ # Untyped data
+ return data
+ load_dispatch[7] = (1, load_undefined)
+
+ def load(self, fp):
+ # load tag dictionary
+
+ self.reset()
+ self.offset = fp.tell()
+
+ i16 = self.i16
+ i32 = self.i32
+
+ for i in range(i16(fp.read(2))):
+
+ ifd = fp.read(12)
+
+ tag, typ = i16(ifd), i16(ifd, 2)
+
+ if Image.DEBUG:
+ from PIL import TiffTags
+ tagname = TiffTags.TAGS.get(tag, "unknown")
+ typname = TiffTags.TYPES.get(typ, "unknown")
+ print("tag: %s (%d)" % (tagname, tag), end=' ')
+ print("- type: %s (%d)" % (typname, typ), end=' ')
+
+ try:
+ dispatch = self.load_dispatch[typ]
+ except KeyError:
+ if Image.DEBUG:
+ print("- unsupported type", typ)
+ continue # ignore unsupported type
+
+ size, handler = dispatch
+
+ size = size * i32(ifd, 4)
+
+ # Get and expand tag value
+ if size > 4:
+ here = fp.tell()
+ if Image.DEBUG:
+ print("Tag Location: %s" % here)
+ fp.seek(i32(ifd, 8))
+ if Image.DEBUG:
+ print("Data Location: %s" % fp.tell())
+ data = ImageFile._safe_read(fp, size)
+ fp.seek(here)
+ else:
+ data = ifd[8:8+size]
+
+ if len(data) != size:
+ warnings.warn("Possibly corrupt EXIF data. "
+ "Expecting to read %d bytes but only got %d. "
+ "Skipping tag %s" % (size, len(data), tag))
+ continue
+
+ self.tagdata[tag] = data
+ self.tagtype[tag] = typ
+
+ if Image.DEBUG:
+ if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
+ ICCPROFILE, XMP):
+ print("- value: <table: %d bytes>" % size)
+ else:
+ print("- value:", self[tag])
+
+ self.next = i32(fp.read(4))
+
+ # save primitives
+
+ def save(self, fp):
+
+ o16 = self.o16
+ o32 = self.o32
+
+ fp.write(o16(len(self.tags)))
+
+ # always write in ascending tag order
+ tags = sorted(self.tags.items())
+
+ directory = []
+ append = directory.append
+
+ offset = fp.tell() + len(self.tags) * 12 + 4
+
+ stripoffsets = None
+
+ # pass 1: convert tags to binary format
+ for tag, value in tags:
+
+ typ = None
+
+ if tag in self.tagtype:
+ typ = self.tagtype[tag]
+
+ if Image.DEBUG:
+ print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value))
+
+ if typ == 1:
+ # byte data
+ if isinstance(value, tuple):
+ data = value = value[-1]
+ else:
+ data = value
+ elif typ == 7:
+ # untyped data
+ data = value = b"".join(value)
+ elif isStringType(value[0]):
+ # string data
+ if isinstance(value, tuple):
+ value = value[-1]
+ typ = 2
+ # was b'\0'.join(str), which led to \x00a\x00b sorts
+ # of strings which I don't see in in the wild tiffs
+ # and doesn't match the tiff spec: 8-bit byte that
+ # contains a 7-bit ASCII code; the last byte must be
+ # NUL (binary zero). Also, I don't think this was well
+ # excersized before.
+ data = value = b"" + value.encode('ascii', 'replace') + b"\0"
+ else:
+ # integer data
+ if tag == STRIPOFFSETS:
+ stripoffsets = len(directory)
+ typ = 4 # to avoid catch-22
+ elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ == 5:
+ # identify rational data fields
+ typ = 5
+ if isinstance(value[0], tuple):
+ # long name for flatten
+ value = tuple(itertools.chain.from_iterable(value))
+ elif not typ:
+ typ = 3
+ for v in value:
+ if v >= 65536:
+ typ = 4
+ if typ == 3:
+ data = b"".join(map(o16, value))
+ else:
+ data = b"".join(map(o32, value))
+
+ if Image.DEBUG:
+ from PIL import TiffTags
+ tagname = TiffTags.TAGS.get(tag, "unknown")
+ typname = TiffTags.TYPES.get(typ, "unknown")
+ print("save: %s (%d)" % (tagname, tag), end=' ')
+ print("- type: %s (%d)" % (typname, typ), end=' ')
+ if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK,
+ ICCPROFILE, XMP):
+ size = len(data)
+ print("- value: <table: %d bytes>" % size)
+ else:
+ print("- value:", value)
+
+ # figure out if data fits into the directory
+ if len(data) == 4:
+ append((tag, typ, len(value), data, b""))
+ elif len(data) < 4:
+ append((tag, typ, len(value), data + (4-len(data))*b"\0", b""))
+ else:
+ count = len(value)
+ if typ == 5:
+ count = count // 2 # adjust for rational data field
+
+ append((tag, typ, count, o32(offset), data))
+ offset += len(data)
+ if offset & 1:
+ offset += 1 # word padding
+
+ # update strip offset data to point beyond auxiliary data
+ if stripoffsets is not None:
+ tag, typ, count, value, data = directory[stripoffsets]
+ assert not data, "multistrip support not yet implemented"
+ value = o32(self.i32(value) + offset)
+ directory[stripoffsets] = tag, typ, count, value, data
+
+ # pass 2: write directory to file
+ for tag, typ, count, value, data in directory:
+ if Image.DEBUG > 1:
+ print(tag, typ, count, repr(value), repr(data))
+ fp.write(o16(tag) + o16(typ) + o32(count) + value)
+
+ # -- overwrite here for multi-page --
+ fp.write(b"\0\0\0\0") # end of directory
+
+ # pass 3: write auxiliary data to file
+ for tag, typ, count, value, data in directory:
+ fp.write(data)
+ if len(data) & 1:
+ fp.write(b"\0")
+
+ return offset
+
+
+##
+# Image plugin for TIFF files.
+
+class TiffImageFile(ImageFile.ImageFile):
+
+ format = "TIFF"
+ format_description = "Adobe TIFF"
+
+ def _open(self):
+ "Open the first image in a TIFF file"
+
+ # Header
+ ifh = self.fp.read(8)
+
+ if ifh[:4] not in PREFIXES:
+ raise SyntaxError("not a TIFF file")
+
+ # image file directory (tag dictionary)
+ self.tag = self.ifd = ImageFileDirectory(ifh[:2])
+
+ # setup frame pointers
+ self.__first = self.__next = self.ifd.i32(ifh, 4)
+ self.__frame = -1
+ self.__fp = self.fp
+
+ if Image.DEBUG:
+ print ("*** TiffImageFile._open ***")
+ print ("- __first:", self.__first)
+ print ("- ifh: ", ifh)
+
+ # and load the first frame
+ self._seek(0)
+
+ def seek(self, frame):
+ "Select a given frame as current image"
+ if frame < 0:
+ frame = 0
+ self._seek(frame)
+ # Create a new core image object on second and
+ # subsequent frames in the image. Image may be
+ # different size/mode.
+ Image._decompression_bomb_check(self.size)
+ self.im = Image.core.new(self.mode, self.size)
+
+ def tell(self):
+ "Return the current frame number"
+ return self._tell()
+
+ def _seek(self, frame):
+ self.fp = self.__fp
+ if frame < self.__frame:
+ # rewind file
+ self.__frame = -1
+ self.__next = self.__first
+ while self.__frame < frame:
+ if not self.__next:
+ raise EOFError("no more images in TIFF file")
+ if Image.DEBUG:
+ print("Seeking to frame %s, on frame %s, __next %s, location: %s" %
+ (frame, self.__frame, self.__next, self.fp.tell()))
+ # reset python3 buffered io handle in case fp
+ # was passed to libtiff, invalidating the buffer
+ self.fp.tell()
+ self.fp.seek(self.__next)
+ if Image.DEBUG:
+ print("Loading tags, location: %s" % self.fp.tell())
+ self.tag.load(self.fp)
+ self.__next = self.tag.next
+ self.__frame += 1
+ self._setup()
+
+ def _tell(self):
+ return self.__frame
+
+ def _decoder(self, rawmode, layer, tile=None):
+ "Setup decoder contexts"
+
+ args = None
+ if rawmode == "RGB" and self._planar_configuration == 2:
+ rawmode = rawmode[layer]
+ compression = self._compression
+ if compression == "raw":
+ args = (rawmode, 0, 1)
+ elif compression == "jpeg":
+ args = rawmode, ""
+ if JPEGTABLES in self.tag:
+ # Hack to handle abbreviated JPEG headers
+ self.tile_prefix = self.tag[JPEGTABLES]
+ elif compression == "packbits":
+ args = rawmode
+ elif compression == "tiff_lzw":
+ args = rawmode
+ if 317 in self.tag:
+ # Section 14: Differencing Predictor
+ self.decoderconfig = (self.tag[PREDICTOR][0],)
+
+ if ICCPROFILE in self.tag:
+ self.info['icc_profile'] = self.tag[ICCPROFILE]
+
+ return args
+
+ def _load_libtiff(self):
+ """ Overload method triggered when we detect a compressed tiff
+ Calls out to libtiff """
+
+ pixel = Image.Image.load(self)
+
+ if self.tile is None:
+ raise IOError("cannot load this image")
+ if not self.tile:
+ return pixel
+
+ self.load_prepare()
+
+ if not len(self.tile) == 1:
+ raise IOError("Not exactly one tile")
+
+ # (self._compression, (extents tuple),
+ # 0, (rawmode, self._compression, fp))
+ ignored, extents, ignored_2, args = self.tile[0]
+ args = args + (self.ifd.offset,)
+ decoder = Image._getdecoder(self.mode, 'libtiff', args,
+ self.decoderconfig)
+ try:
+ decoder.setimage(self.im, extents)
+ except ValueError:
+ raise IOError("Couldn't set the image")
+
+ if hasattr(self.fp, "getvalue"):
+ # We've got a stringio like thing passed in. Yay for all in memory.
+ # The decoder needs the entire file in one shot, so there's not
+ # a lot we can do here other than give it the entire file.
+ # unless we could do something like get the address of the
+ # underlying string for stringio.
+ #
+ # Rearranging for supporting byteio items, since they have a fileno
+ # that returns an IOError if there's no underlying fp. Easier to
+ # dea. with here by reordering.
+ if Image.DEBUG:
+ print ("have getvalue. just sending in a string from getvalue")
+ n, err = decoder.decode(self.fp.getvalue())
+ elif hasattr(self.fp, "fileno"):
+ # we've got a actual file on disk, pass in the fp.
+ if Image.DEBUG:
+ print ("have fileno, calling fileno version of the decoder.")
+ self.fp.seek(0)
+ # 4 bytes, otherwise the trace might error out
+ n, err = decoder.decode(b"fpfp")
+ else:
+ # we have something else.
+ if Image.DEBUG:
+ print ("don't have fileno or getvalue. just reading")
+ # UNDONE -- so much for that buffer size thing.
+ n, err = decoder.decode(self.fp.read())
+
+ self.tile = []
+ self.readonly = 0
+ # libtiff closed the fp in a, we need to close self.fp, if possible
+ if hasattr(self.fp, 'close'):
+ if not self.__next:
+ self.fp.close()
+ self.fp = None # might be shared
+
+ if err < 0:
+ raise IOError(err)
+
+ self.load_end()
+
+ return Image.Image.load(self)
+
+ def _setup(self):
+ "Setup this image object based on current tags"
+
+ if 0xBC01 in self.tag:
+ raise IOError("Windows Media Photo files not yet supported")
+
+ getscalar = self.tag.getscalar
+
+ # extract relevant tags
+ self._compression = COMPRESSION_INFO[getscalar(COMPRESSION, 1)]
+ self._planar_configuration = getscalar(PLANAR_CONFIGURATION, 1)
+
+ # photometric is a required tag, but not everyone is reading
+ # the specification
+ photo = getscalar(PHOTOMETRIC_INTERPRETATION, 0)
+
+ fillorder = getscalar(FILLORDER, 1)
+
+ if Image.DEBUG:
+ print("*** Summary ***")
+ print("- compression:", self._compression)
+ print("- photometric_interpretation:", photo)
+ print("- planar_configuration:", self._planar_configuration)
+ print("- fill_order:", fillorder)
+
+ # size
+ xsize = getscalar(IMAGEWIDTH)
+ ysize = getscalar(IMAGELENGTH)
+ self.size = xsize, ysize
+
+ if Image.DEBUG:
+ print("- size:", self.size)
+
+ format = getscalar(SAMPLEFORMAT, 1)
+
+ # mode: check photometric interpretation and bits per pixel
+ key = (
+ self.tag.prefix, photo, format, fillorder,
+ self.tag.get(BITSPERSAMPLE, (1,)),
+ self.tag.get(EXTRASAMPLES, ())
+ )
+ if Image.DEBUG:
+ print("format key:", key)
+ try:
+ self.mode, rawmode = OPEN_INFO[key]
+ except KeyError:
+ if Image.DEBUG:
+ print("- unsupported format")
+ raise SyntaxError("unknown pixel mode")
+
+ if Image.DEBUG:
+ print("- raw mode:", rawmode)
+ print("- pil mode:", self.mode)
+
+ self.info["compression"] = self._compression
+
+ xres = getscalar(X_RESOLUTION, (1, 1))
+ yres = getscalar(Y_RESOLUTION, (1, 1))
+
+ if xres and not isinstance(xres, tuple):
+ xres = (xres, 1.)
+ if yres and not isinstance(yres, tuple):
+ yres = (yres, 1.)
+ if xres and yres:
+ xres = xres[0] / (xres[1] or 1)
+ yres = yres[0] / (yres[1] or 1)
+ resunit = getscalar(RESOLUTION_UNIT, 1)
+ if resunit == 2: # dots per inch
+ self.info["dpi"] = xres, yres
+ elif resunit == 3: # dots per centimeter. convert to dpi
+ self.info["dpi"] = xres * 2.54, yres * 2.54
+ else: # No absolute unit of measurement
+ self.info["resolution"] = xres, yres
+
+ # build tile descriptors
+ x = y = l = 0
+ self.tile = []
+ if STRIPOFFSETS in self.tag:
+ # striped image
+ offsets = self.tag[STRIPOFFSETS]
+ h = getscalar(ROWSPERSTRIP, ysize)
+ w = self.size[0]
+ if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3",
+ "group4", "tiff_jpeg",
+ "tiff_adobe_deflate",
+ "tiff_thunderscan",
+ "tiff_deflate",
+ "tiff_sgilog",
+ "tiff_sgilog24",
+ "tiff_raw_16"]:
+ # if Image.DEBUG:
+ # print "Activating g4 compression for whole file"
+
+ # Decoder expects entire file as one tile.
+ # There's a buffer size limit in load (64k)
+ # so large g4 images will fail if we use that
+ # function.
+ #
+ # Setup the one tile for the whole image, then
+ # replace the existing load function with our
+ # _load_libtiff function.
+
+ self.load = self._load_libtiff
+
+ # To be nice on memory footprint, if there's a
+ # file descriptor, use that instead of reading
+ # into a string in python.
+
+ # libtiff closes the file descriptor, so pass in a dup.
+ try:
+ fp = hasattr(self.fp, "fileno") and \
+ os.dup(self.fp.fileno())
+ # flush the file descriptor, prevents error on pypy 2.4+
+ # should also eliminate the need for fp.tell for py3
+ # in _seek
+ self.fp.flush()
+ except IOError:
+ # io.BytesIO have a fileno, but returns an IOError if
+ # it doesn't use a file descriptor.
+ fp = False
+
+ # libtiff handles the fillmode for us, so 1;IR should
+ # actually be 1;I. Including the R double reverses the
+ # bits, so stripes of the image are reversed. See
+ # https://github.com/python-pillow/Pillow/issues/279
+ if fillorder == 2:
+ key = (
+ self.tag.prefix, photo, format, 1,
+ self.tag.get(BITSPERSAMPLE, (1,)),
+ self.tag.get(EXTRASAMPLES, ())
+ )
+ if Image.DEBUG:
+ print("format key:", key)
+ # this should always work, since all the
+ # fillorder==2 modes have a corresponding
+ # fillorder=1 mode
+ self.mode, rawmode = OPEN_INFO[key]
+ # libtiff always returns the bytes in native order.
+ # we're expecting image byte order. So, if the rawmode
+ # contains I;16, we need to convert from native to image
+ # byte order.
+ if self.mode in ('I;16B', 'I;16') and 'I;16' in rawmode:
+ rawmode = 'I;16N'
+
+ # Offset in the tile tuple is 0, we go from 0,0 to
+ # w,h, and we only do this once -- eds
+ a = (rawmode, self._compression, fp)
+ self.tile.append(
+ (self._compression,
+ (0, 0, w, ysize),
+ 0, a))
+ a = None
+
+ else:
+ for i in range(len(offsets)):
+ a = self._decoder(rawmode, l, i)
+ self.tile.append(
+ (self._compression,
+ (0, min(y, ysize), w, min(y+h, ysize)),
+ offsets[i], a))
+ if Image.DEBUG:
+ print ("tiles: ", self.tile)
+ y = y + h
+ if y >= self.size[1]:
+ x = y = 0
+ l += 1
+ a = None
+ elif TILEOFFSETS in self.tag:
+ # tiled image
+ w = getscalar(322)
+ h = getscalar(323)
+ a = None
+ for o in self.tag[TILEOFFSETS]:
+ if not a:
+ a = self._decoder(rawmode, l)
+ # FIXME: this doesn't work if the image size
+ # is not a multiple of the tile size...
+ self.tile.append(
+ (self._compression,
+ (x, y, x+w, y+h),
+ o, a))
+ x = x + w
+ if x >= self.size[0]:
+ x, y = 0, y + h
+ if y >= self.size[1]:
+ x = y = 0
+ l += 1
+ a = None
+ else:
+ if Image.DEBUG:
+ print("- unsupported data organization")
+ raise SyntaxError("unknown data organization")
+
+ # fixup palette descriptor
+
+ if self.mode == "P":
+ palette = [o8(a // 256) for a in self.tag[COLORMAP]]
+ self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
+#
+# --------------------------------------------------------------------
+# Write TIFF files
+
+# little endian is default except for image modes with
+# explict big endian byte-order
+
+SAVE_INFO = {
+ # mode => rawmode, byteorder, photometrics,
+ # sampleformat, bitspersample, extra
+ "1": ("1", II, 1, 1, (1,), None),
+ "L": ("L", II, 1, 1, (8,), None),
+ "LA": ("LA", II, 1, 1, (8, 8), 2),
+ "P": ("P", II, 3, 1, (8,), None),
+ "PA": ("PA", II, 3, 1, (8, 8), 2),
+ "I": ("I;32S", II, 1, 2, (32,), None),
+ "I;16": ("I;16", II, 1, 1, (16,), None),
+ "I;16S": ("I;16S", II, 1, 2, (16,), None),
+ "F": ("F;32F", II, 1, 3, (32,), None),
+ "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
+ "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
+ "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
+ "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
+ "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
+ "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
+
+ "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
+ "I;16B": ("I;16B", MM, 1, 1, (16,), None),
+ "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
+ "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
+}
+
+
+def _cvt_res(value):
+ # convert value to TIFF rational number -- (numerator, denominator)
+ if isinstance(value, collections.Sequence):
+ assert(len(value) % 2 == 0)
+ return value
+ if isinstance(value, int):
+ return (value, 1)
+ value = float(value)
+ return (int(value * 65536), 65536)
+
+
+def _save(im, fp, filename):
+
+ try:
+ rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as TIFF" % im.mode)
+
+ ifd = ImageFileDirectory(prefix)
+
+ compression = im.encoderinfo.get('compression', im.info.get('compression',
+ 'raw'))
+
+ libtiff = WRITE_LIBTIFF or compression != 'raw'
+
+ # required for color libtiff images
+ ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)
+
+ # -- multi-page -- skip TIFF header on subsequent pages
+ if not libtiff and fp.tell() == 0:
+ # tiff header (write via IFD to get everything right)
+ # PIL always starts the first IFD at offset 8
+ fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8))
+
+ ifd[IMAGEWIDTH] = im.size[0]
+ ifd[IMAGELENGTH] = im.size[1]
+
+ # write any arbitrary tags passed in as an ImageFileDirectory
+ info = im.encoderinfo.get("tiffinfo", {})
+ if Image.DEBUG:
+ print("Tiffinfo Keys: %s" % info.keys)
+ keys = list(info.keys())
+ for key in keys:
+ ifd[key] = info.get(key)
+ try:
+ ifd.tagtype[key] = info.tagtype[key]
+ except:
+ pass # might not be an IFD, Might not have populated type
+
+ # additions written by Greg Couch, gregc@cgl.ucsf.edu
+ # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
+ if hasattr(im, 'tag'):
+ # preserve tags from original TIFF image file
+ for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
+ IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
+ if key in im.tag:
+ ifd[key] = im.tag[key]
+ ifd.tagtype[key] = im.tag.tagtype.get(key, None)
+
+ # preserve ICC profile (should also work when saving other formats
+ # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
+ if "icc_profile" in im.info:
+ ifd[ICCPROFILE] = im.info["icc_profile"]
+
+ for key, name, cvt in [
+ (IMAGEDESCRIPTION, "description", lambda x: x),
+ (X_RESOLUTION, "resolution", _cvt_res),
+ (Y_RESOLUTION, "resolution", _cvt_res),
+ (X_RESOLUTION, "x_resolution", _cvt_res),
+ (Y_RESOLUTION, "y_resolution", _cvt_res),
+ (RESOLUTION_UNIT, "resolution_unit",
+ lambda x: {"inch": 2, "cm": 3, "centimeter": 3}.get(x, 1)),
+ (SOFTWARE, "software", lambda x: x),
+ (DATE_TIME, "date_time", lambda x: x),
+ (ARTIST, "artist", lambda x: x),
+ (COPYRIGHT, "copyright", lambda x: x)]:
+ name_with_spaces = name.replace("_", " ")
+ if "_" in name and name_with_spaces in im.encoderinfo:
+ warnings.warn("%r is deprecated; use %r instead" %
+ (name_with_spaces, name), DeprecationWarning)
+ ifd[key] = cvt(im.encoderinfo[name.replace("_", " ")])
+ if name in im.encoderinfo:
+ ifd[key] = cvt(im.encoderinfo[name])
+
+ dpi = im.encoderinfo.get("dpi")
+ if dpi:
+ ifd[RESOLUTION_UNIT] = 2
+ ifd[X_RESOLUTION] = _cvt_res(dpi[0])
+ ifd[Y_RESOLUTION] = _cvt_res(dpi[1])
+
+ if bits != (1,):
+ ifd[BITSPERSAMPLE] = bits
+ if len(bits) != 1:
+ ifd[SAMPLESPERPIXEL] = len(bits)
+ if extra is not None:
+ ifd[EXTRASAMPLES] = extra
+ if format != 1:
+ ifd[SAMPLEFORMAT] = format
+
+ ifd[PHOTOMETRIC_INTERPRETATION] = photo
+
+ if im.mode == "P":
+ lut = im.im.getpalette("RGB", "RGB;L")
+ ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut)
+
+ # data orientation
+ stride = len(bits) * ((im.size[0]*bits[0]+7)//8)
+ ifd[ROWSPERSTRIP] = im.size[1]
+ ifd[STRIPBYTECOUNTS] = stride * im.size[1]
+ ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
+ # no compression by default:
+ ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
+
+ if libtiff:
+ if Image.DEBUG:
+ print ("Saving using libtiff encoder")
+ print (ifd.items())
+ _fp = 0
+ if hasattr(fp, "fileno"):
+ try:
+ fp.seek(0)
+ _fp = os.dup(fp.fileno())
+ except io.UnsupportedOperation:
+ pass
+
+ # ICC Profile crashes.
+ blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE]
+ atts = {}
+ # bits per sample is a single short in the tiff directory, not a list.
+ atts[BITSPERSAMPLE] = bits[0]
+ # Merge the ones that we have with (optional) more bits from
+ # the original file, e.g x,y resolution so that we can
+ # save(load('')) == original file.
+ for k, v in itertools.chain(ifd.items(),
+ getattr(im, 'ifd', {}).items()):
+ if k not in atts and k not in blocklist:
+ if type(v[0]) == tuple and len(v) > 1:
+ # A tuple of more than one rational tuples
+ # flatten to floats,
+ # following tiffcp.c->cpTag->TIFF_RATIONAL
+ atts[k] = [float(elt[0])/float(elt[1]) for elt in v]
+ continue
+ if type(v[0]) == tuple and len(v) == 1:
+ # A tuple of one rational tuples
+ # flatten to floats,
+ # following tiffcp.c->cpTag->TIFF_RATIONAL
+ atts[k] = float(v[0][0])/float(v[0][1])
+ continue
+ if (type(v) == tuple and
+ (len(v) > 2 or
+ (len(v) == 2 and v[1] == 0))):
+ # List of ints?
+ # Avoid divide by zero in next if-clause
+ if type(v[0]) in (int, float):
+ atts[k] = list(v)
+ continue
+ if type(v) == tuple and len(v) == 2:
+ # one rational tuple
+ # flatten to float,
+ # following tiffcp.c->cpTag->TIFF_RATIONAL
+ atts[k] = float(v[0])/float(v[1])
+ continue
+ if type(v) == tuple and len(v) == 1:
+ v = v[0]
+ # drop through
+ if isStringType(v):
+ atts[k] = bytes(v.encode('ascii', 'replace')) + b"\0"
+ continue
+ else:
+ # int or similar
+ atts[k] = v
+
+ if Image.DEBUG:
+ print (atts)
+
+ # libtiff always expects the bytes in native order.
+ # we're storing image byte order. So, if the rawmode
+ # contains I;16, we need to convert from native to image
+ # byte order.
+ if im.mode in ('I;16B', 'I;16'):
+ rawmode = 'I;16N'
+
+ a = (rawmode, compression, _fp, filename, atts)
+ # print (im.mode, compression, a, im.encoderconfig)
+ e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig)
+ e.setimage(im.im, (0, 0)+im.size)
+ while True:
+ # undone, change to self.decodermaxblock:
+ l, s, d = e.encode(16*1024)
+ if not _fp:
+ fp.write(d)
+ if s:
+ break
+ if s < 0:
+ raise IOError("encoder error %d when writing image file" % s)
+
+ else:
+ offset = ifd.save(fp)
+
+ ImageFile._save(im, fp, [
+ ("raw", (0, 0)+im.size, offset, (rawmode, stride, 1))
+ ])
+
+ # -- helper for multi-page save --
+ if "_debug_multipage" in im.encoderinfo:
+ # just to access o32 and o16 (using correct byte order)
+ im._debug_multipage = ifd
+
+#
+# --------------------------------------------------------------------
+# Register
+
+Image.register_open("TIFF", TiffImageFile, _accept)
+Image.register_save("TIFF", _save)
+
+Image.register_extension("TIFF", ".tif")
+Image.register_extension("TIFF", ".tiff")
+
+Image.register_mime("TIFF", "image/tiff")
diff --git a/lib/Python/Lib/PIL/TiffTags.py b/lib/Python/Lib/PIL/TiffTags.py
new file mode 100644
index 000000000..d15aa7ebe
--- /dev/null
+++ b/lib/Python/Lib/PIL/TiffTags.py
@@ -0,0 +1,307 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# TIFF tags
+#
+# This module provides clear-text names for various well-known
+# TIFF tags. the TIFF codec works just fine without it.
+#
+# Copyright (c) Secret Labs AB 1999.
+#
+# See the README file for information on usage and redistribution.
+#
+
+##
+# This module provides constants and clear-text names for various
+# well-known TIFF tags.
+##
+
+##
+# Map tag numbers (or tag number, tag value tuples) to tag names.
+
+TAGS = {
+
+ 254: "NewSubfileType",
+ 255: "SubfileType",
+ 256: "ImageWidth",
+ 257: "ImageLength",
+ 258: "BitsPerSample",
+
+ 259: "Compression",
+ (259, 1): "Uncompressed",
+ (259, 2): "CCITT 1d",
+ (259, 3): "Group 3 Fax",
+ (259, 4): "Group 4 Fax",
+ (259, 5): "LZW",
+ (259, 6): "JPEG",
+ (259, 32773): "PackBits",
+
+ 262: "PhotometricInterpretation",
+ (262, 0): "WhiteIsZero",
+ (262, 1): "BlackIsZero",
+ (262, 2): "RGB",
+ (262, 3): "RGB Palette",
+ (262, 4): "Transparency Mask",
+ (262, 5): "CMYK",
+ (262, 6): "YCbCr",
+ (262, 8): "CieLAB",
+ (262, 32803): "CFA", # TIFF/EP, Adobe DNG
+ (262, 32892): "LinearRaw", # Adobe DNG
+
+ 263: "Thresholding",
+ 264: "CellWidth",
+ 265: "CellHeight",
+ 266: "FillOrder",
+ 269: "DocumentName",
+
+ 270: "ImageDescription",
+ 271: "Make",
+ 272: "Model",
+ 273: "StripOffsets",
+ 274: "Orientation",
+ 277: "SamplesPerPixel",
+ 278: "RowsPerStrip",
+ 279: "StripByteCounts",
+
+ 280: "MinSampleValue",
+ 281: "MaxSampleValue",
+ 282: "XResolution",
+ 283: "YResolution",
+ 284: "PlanarConfiguration",
+ (284, 1): "Contigous",
+ (284, 2): "Separate",
+
+ 285: "PageName",
+ 286: "XPosition",
+ 287: "YPosition",
+ 288: "FreeOffsets",
+ 289: "FreeByteCounts",
+
+ 290: "GrayResponseUnit",
+ 291: "GrayResponseCurve",
+ 292: "T4Options",
+ 293: "T6Options",
+ 296: "ResolutionUnit",
+ 297: "PageNumber",
+
+ 301: "TransferFunction",
+ 305: "Software",
+ 306: "DateTime",
+
+ 315: "Artist",
+ 316: "HostComputer",
+ 317: "Predictor",
+ 318: "WhitePoint",
+ 319: "PrimaryChromaticies",
+
+ 320: "ColorMap",
+ 321: "HalftoneHints",
+ 322: "TileWidth",
+ 323: "TileLength",
+ 324: "TileOffsets",
+ 325: "TileByteCounts",
+
+ 332: "InkSet",
+ 333: "InkNames",
+ 334: "NumberOfInks",
+ 336: "DotRange",
+ 337: "TargetPrinter",
+ 338: "ExtraSamples",
+ 339: "SampleFormat",
+
+ 340: "SMinSampleValue",
+ 341: "SMaxSampleValue",
+ 342: "TransferRange",
+
+ 347: "JPEGTables",
+
+ # obsolete JPEG tags
+ 512: "JPEGProc",
+ 513: "JPEGInterchangeFormat",
+ 514: "JPEGInterchangeFormatLength",
+ 515: "JPEGRestartInterval",
+ 517: "JPEGLosslessPredictors",
+ 518: "JPEGPointTransforms",
+ 519: "JPEGQTables",
+ 520: "JPEGDCTables",
+ 521: "JPEGACTables",
+
+ 529: "YCbCrCoefficients",
+ 530: "YCbCrSubSampling",
+ 531: "YCbCrPositioning",
+ 532: "ReferenceBlackWhite",
+
+ # XMP
+ 700: "XMP",
+
+ 33432: "Copyright",
+
+ # various extensions (should check specs for "official" names)
+ 33723: "IptcNaaInfo",
+ 34377: "PhotoshopInfo",
+
+ # Exif IFD
+ 34665: "ExifIFD",
+
+ # ICC Profile
+ 34675: "ICCProfile",
+
+ # Additional Exif Info
+ 33434: "ExposureTime",
+ 33437: "FNumber",
+ 34850: "ExposureProgram",
+ 34852: "SpectralSensitivity",
+ 34853: "GPSInfoIFD",
+ 34855: "ISOSpeedRatings",
+ 34856: "OECF",
+ 34864: "SensitivityType",
+ 34865: "StandardOutputSensitivity",
+ 34866: "RecommendedExposureIndex",
+ 34867: "ISOSpeed",
+ 34868: "ISOSpeedLatitudeyyy",
+ 34869: "ISOSpeedLatitudezzz",
+ 36864: "ExifVersion",
+ 36867: "DateTimeOriginal",
+ 36868: "DateTImeDigitized",
+ 37121: "ComponentsConfiguration",
+ 37122: "CompressedBitsPerPixel",
+ 37377: "ShutterSpeedValue",
+ 37378: "ApertureValue",
+ 37379: "BrightnessValue",
+ 37380: "ExposureBiasValue",
+ 37381: "MaxApertureValue",
+ 37382: "SubjectDistance",
+ 37383: "MeteringMode",
+ 37384: "LightSource",
+ 37385: "Flash",
+ 37386: "FocalLength",
+ 37396: "SubjectArea",
+ 37500: "MakerNote",
+ 37510: "UserComment",
+ 37520: "SubSec",
+ 37521: "SubSecTimeOriginal",
+ 37522: "SubsecTimeDigitized",
+ 40960: "FlashPixVersion",
+ 40961: "ColorSpace",
+ 40962: "PixelXDimension",
+ 40963: "PixelYDimension",
+ 40964: "RelatedSoundFile",
+ 40965: "InteroperabilityIFD",
+ 41483: "FlashEnergy",
+ 41484: "SpatialFrequencyResponse",
+ 41486: "FocalPlaneXResolution",
+ 41487: "FocalPlaneYResolution",
+ 41488: "FocalPlaneResolutionUnit",
+ 41492: "SubjectLocation",
+ 41493: "ExposureIndex",
+ 41495: "SensingMethod",
+ 41728: "FileSource",
+ 41729: "SceneType",
+ 41730: "CFAPattern",
+ 41985: "CustomRendered",
+ 41986: "ExposureMode",
+ 41987: "WhiteBalance",
+ 41988: "DigitalZoomRatio",
+ 41989: "FocalLengthIn35mmFilm",
+ 41990: "SceneCaptureType",
+ 41991: "GainControl",
+ 41992: "Contrast",
+ 41993: "Saturation",
+ 41994: "Sharpness",
+ 41995: "DeviceSettingDescription",
+ 41996: "SubjectDistanceRange",
+ 42016: "ImageUniqueID",
+ 42032: "CameraOwnerName",
+ 42033: "BodySerialNumber",
+ 42034: "LensSpecification",
+ 42035: "LensMake",
+ 42036: "LensModel",
+ 42037: "LensSerialNumber",
+ 42240: "Gamma",
+
+ # MP Info
+ 45056: "MPFVersion",
+ 45057: "NumberOfImages",
+ 45058: "MPEntry",
+ 45059: "ImageUIDList",
+ 45060: "TotalFrames",
+ 45313: "MPIndividualNum",
+ 45569: "PanOrientation",
+ 45570: "PanOverlap_H",
+ 45571: "PanOverlap_V",
+ 45572: "BaseViewpointNum",
+ 45573: "ConvergenceAngle",
+ 45574: "BaselineLength",
+ 45575: "VerticalDivergence",
+ 45576: "AxisDistance_X",
+ 45577: "AxisDistance_Y",
+ 45578: "AxisDistance_Z",
+ 45579: "YawAngle",
+ 45580: "PitchAngle",
+ 45581: "RollAngle",
+
+ # Adobe DNG
+ 50706: "DNGVersion",
+ 50707: "DNGBackwardVersion",
+ 50708: "UniqueCameraModel",
+ 50709: "LocalizedCameraModel",
+ 50710: "CFAPlaneColor",
+ 50711: "CFALayout",
+ 50712: "LinearizationTable",
+ 50713: "BlackLevelRepeatDim",
+ 50714: "BlackLevel",
+ 50715: "BlackLevelDeltaH",
+ 50716: "BlackLevelDeltaV",
+ 50717: "WhiteLevel",
+ 50718: "DefaultScale",
+ 50719: "DefaultCropOrigin",
+ 50720: "DefaultCropSize",
+ 50778: "CalibrationIlluminant1",
+ 50779: "CalibrationIlluminant2",
+ 50721: "ColorMatrix1",
+ 50722: "ColorMatrix2",
+ 50723: "CameraCalibration1",
+ 50724: "CameraCalibration2",
+ 50725: "ReductionMatrix1",
+ 50726: "ReductionMatrix2",
+ 50727: "AnalogBalance",
+ 50728: "AsShotNeutral",
+ 50729: "AsShotWhiteXY",
+ 50730: "BaselineExposure",
+ 50731: "BaselineNoise",
+ 50732: "BaselineSharpness",
+ 50733: "BayerGreenSplit",
+ 50734: "LinearResponseLimit",
+ 50735: "CameraSerialNumber",
+ 50736: "LensInfo",
+ 50737: "ChromaBlurRadius",
+ 50738: "AntiAliasStrength",
+ 50740: "DNGPrivateData",
+ 50741: "MakerNoteSafety",
+ 50780: "BestQualityScale",
+
+ # ImageJ
+ 50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
+ 50839: "ImageJMetaData", # private tag registered with Adobe
+}
+
+##
+# Map type numbers to type names.
+
+TYPES = {
+
+ 1: "byte",
+ 2: "ascii",
+ 3: "short",
+ 4: "long",
+ 5: "rational",
+ 6: "signed byte",
+ 7: "undefined",
+ 8: "signed short",
+ 9: "signed long",
+ 10: "signed rational",
+ 11: "float",
+ 12: "double",
+
+}
diff --git a/lib/Python/Lib/PIL/WalImageFile.py b/lib/Python/Lib/PIL/WalImageFile.py
new file mode 100644
index 000000000..fc2bb30a7
--- /dev/null
+++ b/lib/Python/Lib/PIL/WalImageFile.py
@@ -0,0 +1,131 @@
+# The Python Imaging Library.
+# $Id$
+#
+# WAL file handling
+#
+# History:
+# 2003-04-23 fl created
+#
+# Copyright (c) 2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# NOTE: This format cannot be automatically recognized, so the reader
+# is not registered for use with Image.open(). To open a WAL file, use
+# the WalImageFile.open() function instead.
+
+# This reader is based on the specification available from:
+# http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
+# and has been tested with a few sample files found using google.
+
+from __future__ import print_function
+
+from PIL import Image, _binary
+
+try:
+ import builtins
+except ImportError:
+ import __builtin__
+ builtins = __builtin__
+
+i32 = _binary.i32le
+
+
+##
+# Load texture from a Quake2 WAL texture file.
+# <p>
+# By default, a Quake2 standard palette is attached to the texture.
+# To override the palette, use the <b>putpalette</b> method.
+#
+# @param filename WAL file name, or an opened file handle.
+# @return An image instance.
+
+def open(filename):
+ # FIXME: modify to return a WalImageFile instance instead of
+ # plain Image object ?
+
+ if hasattr(filename, "read"):
+ fp = filename
+ else:
+ fp = builtins.open(filename, "rb")
+
+ # read header fields
+ header = fp.read(32+24+32+12)
+ size = i32(header, 32), i32(header, 36)
+ offset = i32(header, 40)
+
+ # load pixel data
+ fp.seek(offset)
+
+ im = Image.frombytes("P", size, fp.read(size[0] * size[1]))
+ im.putpalette(quake2palette)
+
+ im.format = "WAL"
+ im.format_description = "Quake2 Texture"
+
+ # strings are null-terminated
+ im.info["name"] = header[:32].split(b"\0", 1)[0]
+ next_name = header[56:56+32].split(b"\0", 1)[0]
+ if next_name:
+ im.info["next_name"] = next_name
+
+ return im
+
+
+quake2palette = (
+ # default palette taken from piffo 0.93 by Hans HÀggström
+ b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e"
+ b"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f"
+ b"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c"
+ b"\x24\x1e\x13\x22\x1c\x12\x20\x1b\x12\x1f\x1a\x10\x1d\x19\x10\x1b"
+ b"\x17\x0f\x1a\x16\x0f\x18\x14\x0d\x17\x13\x0d\x16\x12\x0d\x14\x10"
+ b"\x0b\x13\x0f\x0b\x10\x0d\x0a\x0f\x0b\x0a\x0d\x0b\x07\x0b\x0a\x07"
+ b"\x23\x23\x26\x22\x22\x25\x22\x20\x23\x21\x1f\x22\x20\x1e\x20\x1f"
+ b"\x1d\x1e\x1d\x1b\x1c\x1b\x1a\x1a\x1a\x19\x19\x18\x17\x17\x17\x16"
+ b"\x16\x14\x14\x14\x13\x13\x13\x10\x10\x10\x0f\x0f\x0f\x0d\x0d\x0d"
+ b"\x2d\x28\x20\x29\x24\x1c\x27\x22\x1a\x25\x1f\x17\x38\x2e\x1e\x31"
+ b"\x29\x1a\x2c\x25\x17\x26\x20\x14\x3c\x30\x14\x37\x2c\x13\x33\x28"
+ b"\x12\x2d\x24\x10\x28\x1f\x0f\x22\x1a\x0b\x1b\x14\x0a\x13\x0f\x07"
+ b"\x31\x1a\x16\x30\x17\x13\x2e\x16\x10\x2c\x14\x0d\x2a\x12\x0b\x27"
+ b"\x0f\x0a\x25\x0f\x07\x21\x0d\x01\x1e\x0b\x01\x1c\x0b\x01\x1a\x0b"
+ b"\x01\x18\x0a\x01\x16\x0a\x01\x13\x0a\x01\x10\x07\x01\x0d\x07\x01"
+ b"\x29\x23\x1e\x27\x21\x1c\x26\x20\x1b\x25\x1f\x1a\x23\x1d\x19\x21"
+ b"\x1c\x18\x20\x1b\x17\x1e\x19\x16\x1c\x18\x14\x1b\x17\x13\x19\x14"
+ b"\x10\x17\x13\x0f\x14\x10\x0d\x12\x0f\x0b\x0f\x0b\x0a\x0b\x0a\x07"
+ b"\x26\x1a\x0f\x23\x19\x0f\x20\x17\x0f\x1c\x16\x0f\x19\x13\x0d\x14"
+ b"\x10\x0b\x10\x0d\x0a\x0b\x0a\x07\x33\x22\x1f\x35\x29\x26\x37\x2f"
+ b"\x2d\x39\x35\x34\x37\x39\x3a\x33\x37\x39\x30\x34\x36\x2b\x31\x34"
+ b"\x27\x2e\x31\x22\x2b\x2f\x1d\x28\x2c\x17\x25\x2a\x0f\x20\x26\x0d"
+ b"\x1e\x25\x0b\x1c\x22\x0a\x1b\x20\x07\x19\x1e\x07\x17\x1b\x07\x14"
+ b"\x18\x01\x12\x16\x01\x0f\x12\x01\x0b\x0d\x01\x07\x0a\x01\x01\x01"
+ b"\x2c\x21\x21\x2a\x1f\x1f\x29\x1d\x1d\x27\x1c\x1c\x26\x1a\x1a\x24"
+ b"\x18\x18\x22\x17\x17\x21\x16\x16\x1e\x13\x13\x1b\x12\x12\x18\x10"
+ b"\x10\x16\x0d\x0d\x12\x0b\x0b\x0d\x0a\x0a\x0a\x07\x07\x01\x01\x01"
+ b"\x2e\x30\x29\x2d\x2e\x27\x2b\x2c\x26\x2a\x2a\x24\x28\x29\x23\x27"
+ b"\x27\x21\x26\x26\x1f\x24\x24\x1d\x22\x22\x1c\x1f\x1f\x1a\x1c\x1c"
+ b"\x18\x19\x19\x16\x17\x17\x13\x13\x13\x10\x0f\x0f\x0d\x0b\x0b\x0a"
+ b"\x30\x1e\x1b\x2d\x1c\x19\x2c\x1a\x17\x2a\x19\x14\x28\x17\x13\x26"
+ b"\x16\x10\x24\x13\x0f\x21\x12\x0d\x1f\x10\x0b\x1c\x0f\x0a\x19\x0d"
+ b"\x0a\x16\x0b\x07\x12\x0a\x07\x0f\x07\x01\x0a\x01\x01\x01\x01\x01"
+ b"\x28\x29\x38\x26\x27\x36\x25\x26\x34\x24\x24\x31\x22\x22\x2f\x20"
+ b"\x21\x2d\x1e\x1f\x2a\x1d\x1d\x27\x1b\x1b\x25\x19\x19\x21\x17\x17"
+ b"\x1e\x14\x14\x1b\x13\x12\x17\x10\x0f\x13\x0d\x0b\x0f\x0a\x07\x07"
+ b"\x2f\x32\x29\x2d\x30\x26\x2b\x2e\x24\x29\x2c\x21\x27\x2a\x1e\x25"
+ b"\x28\x1c\x23\x26\x1a\x21\x25\x18\x1e\x22\x14\x1b\x1f\x10\x19\x1c"
+ b"\x0d\x17\x1a\x0a\x13\x17\x07\x10\x13\x01\x0d\x0f\x01\x0a\x0b\x01"
+ b"\x01\x3f\x01\x13\x3c\x0b\x1b\x39\x10\x20\x35\x14\x23\x31\x17\x23"
+ b"\x2d\x18\x23\x29\x18\x3f\x3f\x3f\x3f\x3f\x39\x3f\x3f\x31\x3f\x3f"
+ b"\x2a\x3f\x3f\x20\x3f\x3f\x14\x3f\x3c\x12\x3f\x39\x0f\x3f\x35\x0b"
+ b"\x3f\x32\x07\x3f\x2d\x01\x3d\x2a\x01\x3b\x26\x01\x39\x21\x01\x37"
+ b"\x1d\x01\x34\x1a\x01\x32\x16\x01\x2f\x12\x01\x2d\x0f\x01\x2a\x0b"
+ b"\x01\x27\x07\x01\x23\x01\x01\x1d\x01\x01\x17\x01\x01\x10\x01\x01"
+ b"\x3d\x01\x01\x19\x19\x3f\x3f\x01\x01\x01\x01\x3f\x16\x16\x13\x10"
+ b"\x10\x0f\x0d\x0d\x0b\x3c\x2e\x2a\x36\x27\x20\x30\x21\x18\x29\x1b"
+ b"\x10\x3c\x39\x37\x37\x32\x2f\x31\x2c\x28\x2b\x26\x21\x30\x22\x20"
+)
+
+if __name__ == "__main__":
+ im = open("../hacks/sample.wal")
+ print(im.info, im.mode, im.size)
+ im.save("../out.png")
diff --git a/lib/Python/Lib/PIL/WebPImagePlugin.py b/lib/Python/Lib/PIL/WebPImagePlugin.py
new file mode 100644
index 000000000..78a7a5319
--- /dev/null
+++ b/lib/Python/Lib/PIL/WebPImagePlugin.py
@@ -0,0 +1,80 @@
+from PIL import Image
+from PIL import ImageFile
+from io import BytesIO
+from PIL import _webp
+
+
+_VALID_WEBP_MODES = {
+ "RGB": True,
+ "RGBA": True,
+ }
+
+_VP8_MODES_BY_IDENTIFIER = {
+ b"VP8 ": "RGB",
+ b"VP8X": "RGBA",
+ b"VP8L": "RGBA", # lossless
+ }
+
+
+def _accept(prefix):
+ is_riff_file_format = prefix[:4] == b"RIFF"
+ is_webp_file = prefix[8:12] == b"WEBP"
+ is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER
+
+ return is_riff_file_format and is_webp_file and is_valid_vp8_mode
+
+
+class WebPImageFile(ImageFile.ImageFile):
+
+ format = "WEBP"
+ format_description = "WebP image"
+
+ def _open(self):
+ data, width, height, self.mode, icc_profile, exif = \
+ _webp.WebPDecode(self.fp.read())
+
+ if icc_profile:
+ self.info["icc_profile"] = icc_profile
+ if exif:
+ self.info["exif"] = exif
+
+ self.size = width, height
+ self.fp = BytesIO(data)
+ self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
+
+ def _getexif(self):
+ from PIL.JpegImagePlugin import _getexif
+ return _getexif(self)
+
+
+def _save(im, fp, filename):
+ image_mode = im.mode
+ if im.mode not in _VALID_WEBP_MODES:
+ raise IOError("cannot write mode %s as WEBP" % image_mode)
+
+ lossless = im.encoderinfo.get("lossless", False)
+ quality = im.encoderinfo.get("quality", 80)
+ icc_profile = im.encoderinfo.get("icc_profile", "")
+ exif = im.encoderinfo.get("exif", "")
+
+ data = _webp.WebPEncode(
+ im.tobytes(),
+ im.size[0],
+ im.size[1],
+ lossless,
+ float(quality),
+ im.mode,
+ icc_profile,
+ exif
+ )
+ if data is None:
+ raise IOError("cannot write file as WEBP (encoder returned None)")
+
+ fp.write(data)
+
+
+Image.register_open("WEBP", WebPImageFile, _accept)
+Image.register_save("WEBP", _save)
+
+Image.register_extension("WEBP", ".webp")
+Image.register_mime("WEBP", "image/webp")
diff --git a/lib/Python/Lib/PIL/WmfImagePlugin.py b/lib/Python/Lib/PIL/WmfImagePlugin.py
new file mode 100644
index 000000000..6146c1560
--- /dev/null
+++ b/lib/Python/Lib/PIL/WmfImagePlugin.py
@@ -0,0 +1,173 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# WMF stub codec
+#
+# history:
+# 1996-12-14 fl Created
+# 2004-02-22 fl Turned into a stub driver
+# 2004-02-23 fl Added EMF support
+#
+# Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.2"
+
+from PIL import Image, ImageFile, _binary
+
+_handler = None
+
+if str != bytes:
+ long = int
+
+
+##
+# Install application-specific WMF image handler.
+#
+# @param handler Handler object.
+
+def register_handler(handler):
+ global _handler
+ _handler = handler
+
+if hasattr(Image.core, "drawwmf"):
+ # install default handler (windows only)
+
+ class WmfHandler:
+
+ def open(self, im):
+ im.mode = "RGB"
+ self.bbox = im.info["wmf_bbox"]
+
+ def load(self, im):
+ im.fp.seek(0) # rewind
+ return Image.frombytes(
+ "RGB", im.size,
+ Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
+ "raw", "BGR", (im.size[0]*3 + 3) & -4, -1
+ )
+
+ register_handler(WmfHandler())
+
+# --------------------------------------------------------------------
+
+word = _binary.i16le
+
+
+def short(c, o=0):
+ v = word(c, o)
+ if v >= 32768:
+ v -= 65536
+ return v
+
+dword = _binary.i32le
+
+
+#
+# --------------------------------------------------------------------
+# Read WMF file
+
+def _accept(prefix):
+ return (
+ prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or
+ prefix[:4] == b"\x01\x00\x00\x00"
+ )
+
+
+##
+# Image plugin for Windows metafiles.
+
+class WmfStubImageFile(ImageFile.StubImageFile):
+
+ format = "WMF"
+ format_description = "Windows Metafile"
+
+ def _open(self):
+
+ # check placable header
+ s = self.fp.read(80)
+
+ if s[:6] == b"\xd7\xcd\xc6\x9a\x00\x00":
+
+ # placeable windows metafile
+
+ # get units per inch
+ inch = word(s, 14)
+
+ # get bounding box
+ x0 = short(s, 6)
+ y0 = short(s, 8)
+ x1 = short(s, 10)
+ y1 = short(s, 12)
+
+ # normalize size to 72 dots per inch
+ size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch
+
+ self.info["wmf_bbox"] = x0, y0, x1, y1
+
+ self.info["dpi"] = 72
+
+ # print self.mode, self.size, self.info
+
+ # sanity check (standard metafile header)
+ if s[22:26] != b"\x01\x00\t\x00":
+ raise SyntaxError("Unsupported WMF file format")
+
+ elif dword(s) == 1 and s[40:44] == b" EMF":
+ # enhanced metafile
+
+ # get bounding box
+ x0 = dword(s, 8)
+ y0 = dword(s, 12)
+ x1 = dword(s, 16)
+ y1 = dword(s, 20)
+
+ # get frame (in 0.01 millimeter units)
+ frame = dword(s, 24), dword(s, 28), dword(s, 32), dword(s, 36)
+
+ # normalize size to 72 dots per inch
+ size = x1 - x0, y1 - y0
+
+ # calculate dots per inch from bbox and frame
+ xdpi = 2540 * (x1 - y0) // (frame[2] - frame[0])
+ ydpi = 2540 * (y1 - y0) // (frame[3] - frame[1])
+
+ self.info["wmf_bbox"] = x0, y0, x1, y1
+
+ if xdpi == ydpi:
+ self.info["dpi"] = xdpi
+ else:
+ self.info["dpi"] = xdpi, ydpi
+
+ else:
+ raise SyntaxError("Unsupported file format")
+
+ self.mode = "RGB"
+ self.size = size
+
+ loader = self._load()
+ if loader:
+ loader.open(self)
+
+ def _load(self):
+ return _handler
+
+
+def _save(im, fp, filename):
+ if _handler is None or not hasattr("_handler", "save"):
+ raise IOError("WMF save handler not installed")
+ _handler.save(im, fp, filename)
+
+#
+# --------------------------------------------------------------------
+# Registry stuff
+
+Image.register_open(WmfStubImageFile.format, WmfStubImageFile, _accept)
+Image.register_save(WmfStubImageFile.format, _save)
+
+Image.register_extension(WmfStubImageFile.format, ".wmf")
+Image.register_extension(WmfStubImageFile.format, ".emf")
diff --git a/lib/Python/Lib/PIL/XVThumbImagePlugin.py b/lib/Python/Lib/PIL/XVThumbImagePlugin.py
new file mode 100644
index 000000000..5cf1386fd
--- /dev/null
+++ b/lib/Python/Lib/PIL/XVThumbImagePlugin.py
@@ -0,0 +1,75 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# XV Thumbnail file handler by Charles E. "Gene" Cash
+# (gcash@magicnet.net)
+#
+# see xvcolor.c and xvbrowse.c in the sources to John Bradley's XV,
+# available from ftp://ftp.cis.upenn.edu/pub/xv/
+#
+# history:
+# 98-08-15 cec created (b/w only)
+# 98-12-09 cec added color palette
+# 98-12-28 fl added to PIL (with only a few very minor modifications)
+#
+# To do:
+# FIXME: make save work (this requires quantization support)
+#
+
+__version__ = "0.1"
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+
+o8 = _binary.o8
+
+# standard color palette for thumbnails (RGB332)
+PALETTE = b""
+for r in range(8):
+ for g in range(8):
+ for b in range(4):
+ PALETTE = PALETTE + (o8((r*255)//7)+o8((g*255)//7)+o8((b*255)//3))
+
+
+##
+# Image plugin for XV thumbnail images.
+
+class XVThumbImageFile(ImageFile.ImageFile):
+
+ format = "XVThumb"
+ format_description = "XV thumbnail image"
+
+ def _open(self):
+
+ # check magic
+ s = self.fp.read(6)
+ if s != b"P7 332":
+ raise SyntaxError("not an XV thumbnail file")
+
+ # Skip to beginning of next line
+ self.fp.readline()
+
+ # skip info comments
+ while True:
+ s = self.fp.readline()
+ if not s:
+ raise SyntaxError("Unexpected EOF reading XV thumbnail file")
+ if s[0] != b'#':
+ break
+
+ # parse header line (already read)
+ s = s.strip().split()
+
+ self.mode = "P"
+ self.size = int(s[0:1]), int(s[1:2])
+
+ self.palette = ImagePalette.raw("RGB", PALETTE)
+
+ self.tile = [
+ ("raw", (0, 0)+self.size,
+ self.fp.tell(), (self.mode, 0, 1)
+ )]
+
+# --------------------------------------------------------------------
+
+Image.register_open("XVThumb", XVThumbImageFile)
diff --git a/lib/Python/Lib/PIL/XbmImagePlugin.py b/lib/Python/Lib/PIL/XbmImagePlugin.py
new file mode 100644
index 000000000..604ba15a8
--- /dev/null
+++ b/lib/Python/Lib/PIL/XbmImagePlugin.py
@@ -0,0 +1,96 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# XBM File handling
+#
+# History:
+# 1995-09-08 fl Created
+# 1996-11-01 fl Added save support
+# 1997-07-07 fl Made header parser more tolerant
+# 1997-07-22 fl Fixed yet another parser bug
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
+# 2001-05-13 fl Added hotspot handling (based on code from Bernhard Herzog)
+# 2004-02-24 fl Allow some whitespace before first #define
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1996-1997 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+__version__ = "0.6"
+
+import re
+from PIL import Image, ImageFile
+
+# XBM header
+xbm_head = re.compile(
+ b"\s*#define[ \t]+[^_]*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
+ b"#define[ \t]+[^_]*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
+ b"(?P<hotspot>"
+ b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
+ b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
+ b")?"
+ b"[\\000-\\377]*_bits\\[\\]"
+)
+
+
+def _accept(prefix):
+ return prefix.lstrip()[:7] == b"#define"
+
+
+##
+# Image plugin for X11 bitmaps.
+
+class XbmImageFile(ImageFile.ImageFile):
+
+ format = "XBM"
+ format_description = "X11 Bitmap"
+
+ def _open(self):
+
+ m = xbm_head.match(self.fp.read(512))
+
+ if m:
+
+ xsize = int(m.group("width"))
+ ysize = int(m.group("height"))
+
+ if m.group("hotspot"):
+ self.info["hotspot"] = (
+ int(m.group("xhot")), int(m.group("yhot"))
+ )
+
+ self.mode = "1"
+ self.size = xsize, ysize
+
+ self.tile = [("xbm", (0, 0)+self.size, m.end(), None)]
+
+
+def _save(im, fp, filename):
+
+ if im.mode != "1":
+ raise IOError("cannot write mode %s as XBM" % im.mode)
+
+ fp.write(("#define im_width %d\n" % im.size[0]).encode('ascii'))
+ fp.write(("#define im_height %d\n" % im.size[1]).encode('ascii'))
+
+ hotspot = im.encoderinfo.get("hotspot")
+ if hotspot:
+ fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode('ascii'))
+ fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode('ascii'))
+
+ fp.write(b"static char im_bits[] = {\n")
+
+ ImageFile._save(im, fp, [("xbm", (0, 0)+im.size, 0, None)])
+
+ fp.write(b"};\n")
+
+
+Image.register_open("XBM", XbmImageFile, _accept)
+Image.register_save("XBM", _save)
+
+Image.register_extension("XBM", ".xbm")
+
+Image.register_mime("XBM", "image/xbm")
diff --git a/lib/Python/Lib/PIL/XpmImagePlugin.py b/lib/Python/Lib/PIL/XpmImagePlugin.py
new file mode 100644
index 000000000..517580895
--- /dev/null
+++ b/lib/Python/Lib/PIL/XpmImagePlugin.py
@@ -0,0 +1,131 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# XPM File handling
+#
+# History:
+# 1996-12-29 fl Created
+# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
+#
+# Copyright (c) Secret Labs AB 1997-2001.
+# Copyright (c) Fredrik Lundh 1996-2001.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.2"
+
+
+import re
+from PIL import Image, ImageFile, ImagePalette
+from PIL._binary import i8, o8
+
+# XPM header
+xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)")
+
+
+def _accept(prefix):
+ return prefix[:9] == b"/* XPM */"
+
+
+##
+# Image plugin for X11 pixel maps.
+
+class XpmImageFile(ImageFile.ImageFile):
+
+ format = "XPM"
+ format_description = "X11 Pixel Map"
+
+ def _open(self):
+
+ if not _accept(self.fp.read(9)):
+ raise SyntaxError("not an XPM file")
+
+ # skip forward to next string
+ while True:
+ s = self.fp.readline()
+ if not s:
+ raise SyntaxError("broken XPM file")
+ m = xpm_head.match(s)
+ if m:
+ break
+
+ self.size = int(m.group(1)), int(m.group(2))
+
+ pal = int(m.group(3))
+ bpp = int(m.group(4))
+
+ if pal > 256 or bpp != 1:
+ raise ValueError("cannot read this XPM file")
+
+ #
+ # load palette description
+
+ palette = [b"\0\0\0"] * 256
+
+ for i in range(pal):
+
+ s = self.fp.readline()
+ if s[-2:] == b'\r\n':
+ s = s[:-2]
+ elif s[-1:] in b'\r\n':
+ s = s[:-1]
+
+ c = i8(s[1])
+ s = s[2:-2].split()
+
+ for i in range(0, len(s), 2):
+
+ if s[i] == b"c":
+
+ # process colour key
+ rgb = s[i+1]
+ if rgb == b"None":
+ self.info["transparency"] = c
+ elif rgb[0:1] == b"#":
+ # FIXME: handle colour names (see ImagePalette.py)
+ rgb = int(rgb[1:], 16)
+ palette[c] = (o8((rgb >> 16) & 255) +
+ o8((rgb >> 8) & 255) +
+ o8(rgb & 255))
+ else:
+ # unknown colour
+ raise ValueError("cannot read this XPM file")
+ break
+
+ else:
+
+ # missing colour key
+ raise ValueError("cannot read this XPM file")
+
+ self.mode = "P"
+ self.palette = ImagePalette.raw("RGB", b"".join(palette))
+
+ self.tile = [("raw", (0, 0)+self.size, self.fp.tell(), ("P", 0, 1))]
+
+ def load_read(self, bytes):
+
+ #
+ # load all image data in one chunk
+
+ xsize, ysize = self.size
+
+ s = [None] * ysize
+
+ for i in range(ysize):
+ s[i] = self.fp.readline()[1:xsize+1].ljust(xsize)
+
+ self.fp = None
+
+ return b"".join(s)
+
+#
+# Registry
+
+Image.register_open("XPM", XpmImageFile, _accept)
+
+Image.register_extension("XPM", ".xpm")
+
+Image.register_mime("XPM", "image/xpm")
diff --git a/lib/Python/Lib/PIL/__init__.py b/lib/Python/Lib/PIL/__init__.py
new file mode 100644
index 000000000..4dce12d33
--- /dev/null
+++ b/lib/Python/Lib/PIL/__init__.py
@@ -0,0 +1,58 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# package placeholder
+#
+# Copyright (c) 1999 by Secret Labs AB.
+#
+# See the README file for information on usage and redistribution.
+#
+
+# ;-)
+
+VERSION = '1.1.7' # PIL version
+PILLOW_VERSION = '2.8.1' # Pillow
+
+_plugins = ['BmpImagePlugin',
+ 'BufrStubImagePlugin',
+ 'CurImagePlugin',
+ 'DcxImagePlugin',
+ 'EpsImagePlugin',
+ 'FitsStubImagePlugin',
+ 'FliImagePlugin',
+ 'FpxImagePlugin',
+ 'GbrImagePlugin',
+ 'GifImagePlugin',
+ 'GribStubImagePlugin',
+ 'Hdf5StubImagePlugin',
+ 'IcnsImagePlugin',
+ 'IcoImagePlugin',
+ 'ImImagePlugin',
+ 'ImtImagePlugin',
+ 'IptcImagePlugin',
+ 'JpegImagePlugin',
+ 'Jpeg2KImagePlugin',
+ 'McIdasImagePlugin',
+ 'MicImagePlugin',
+ 'MpegImagePlugin',
+ 'MpoImagePlugin',
+ 'MspImagePlugin',
+ 'PalmImagePlugin',
+ 'PcdImagePlugin',
+ 'PcxImagePlugin',
+ 'PdfImagePlugin',
+ 'PixarImagePlugin',
+ 'PngImagePlugin',
+ 'PpmImagePlugin',
+ 'PsdImagePlugin',
+ 'SgiImagePlugin',
+ 'SpiderImagePlugin',
+ 'SunImagePlugin',
+ 'TgaImagePlugin',
+ 'TiffImagePlugin',
+ 'WebPImagePlugin',
+ 'WmfImagePlugin',
+ 'XbmImagePlugin',
+ 'XpmImagePlugin',
+ 'XVThumbImagePlugin']
diff --git a/lib/Python/Lib/PIL/_binary.py b/lib/Python/Lib/PIL/_binary.py
new file mode 100644
index 000000000..2f5e8ffd4
--- /dev/null
+++ b/lib/Python/Lib/PIL/_binary.py
@@ -0,0 +1,76 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Binary input/output support routines.
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1995-2003 by Fredrik Lundh
+# Copyright (c) 2012 by Brian Crowell
+#
+# See the README file for information on usage and redistribution.
+#
+
+from struct import unpack, pack
+
+if bytes is str:
+ def i8(c):
+ return ord(c)
+
+ def o8(i):
+ return chr(i & 255)
+else:
+ def i8(c):
+ return c if c.__class__ is int else c[0]
+
+ def o8(i):
+ return bytes((i & 255,))
+
+
+# Input, le = little endian, be = big endian
+# TODO: replace with more readable struct.unpack equivalent
+def i16le(c, o=0):
+ """
+ Converts a 2-bytes (16 bits) string to an integer.
+
+ c: string containing bytes to convert
+ o: offset of bytes to convert in string
+ """
+ return unpack("<H", c[o:o+2])[0]
+
+
+def i32le(c, o=0):
+ """
+ Converts a 4-bytes (32 bits) string to an integer.
+
+ c: string containing bytes to convert
+ o: offset of bytes to convert in string
+ """
+ return unpack("<I", c[o:o+4])[0]
+
+
+def i16be(c, o=0):
+ return unpack(">H", c[o:o+2])[0]
+
+
+def i32be(c, o=0):
+ return unpack(">I", c[o:o+4])[0]
+
+
+# Output, le = little endian, be = big endian
+def o16le(i):
+ return pack("<H", i)
+
+
+def o32le(i):
+ return pack("<I", i)
+
+
+def o16be(i):
+ return pack(">H", i)
+
+
+def o32be(i):
+ return pack(">I", i)
+
+# End of file
diff --git a/lib/Python/Lib/PIL/_util.py b/lib/Python/Lib/PIL/_util.py
new file mode 100644
index 000000000..51c6f6887
--- /dev/null
+++ b/lib/Python/Lib/PIL/_util.py
@@ -0,0 +1,27 @@
+import os
+
+if bytes is str:
+ def isStringType(t):
+ return isinstance(t, basestring)
+
+ def isPath(f):
+ return isinstance(f, basestring)
+else:
+ def isStringType(t):
+ return isinstance(t, str)
+
+ def isPath(f):
+ return isinstance(f, (bytes, str))
+
+
+# Checks if an object is a string, and that it points to a directory.
+def isDirectory(f):
+ return isPath(f) and os.path.isdir(f)
+
+
+class deferred_error(object):
+ def __init__(self, ex):
+ self.ex = ex
+
+ def __getattr__(self, elt):
+ raise self.ex
diff --git a/module/lib/SafeEval.py b/lib/Python/Lib/SafeEval.py
index 8fc57f261..8fc57f261 100644
--- a/module/lib/SafeEval.py
+++ b/lib/Python/Lib/SafeEval.py
diff --git a/module/lib/Unzip.py b/lib/Python/Lib/Unzip.py
index f56fbe751..f56fbe751 100644
--- a/module/lib/Unzip.py
+++ b/lib/Python/Lib/Unzip.py
diff --git a/module/lib/__init__.py b/lib/Python/Lib/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/lib/__init__.py
+++ b/lib/Python/Lib/__init__.py
diff --git a/module/lib/beaker/__init__.py b/lib/Python/Lib/beaker/__init__.py
index 792d60054..792d60054 100644
--- a/module/lib/beaker/__init__.py
+++ b/lib/Python/Lib/beaker/__init__.py
diff --git a/module/lib/beaker/cache.py b/lib/Python/Lib/beaker/cache.py
index 4a96537ff..4a96537ff 100644
--- a/module/lib/beaker/cache.py
+++ b/lib/Python/Lib/beaker/cache.py
diff --git a/module/lib/beaker/container.py b/lib/Python/Lib/beaker/container.py
index 515e97af6..515e97af6 100644
--- a/module/lib/beaker/container.py
+++ b/lib/Python/Lib/beaker/container.py
diff --git a/module/lib/beaker/converters.py b/lib/Python/Lib/beaker/converters.py
index f0ad34963..f0ad34963 100644
--- a/module/lib/beaker/converters.py
+++ b/lib/Python/Lib/beaker/converters.py
diff --git a/module/lib/beaker/crypto/__init__.py b/lib/Python/Lib/beaker/crypto/__init__.py
index 3e26b0c13..3e26b0c13 100644
--- a/module/lib/beaker/crypto/__init__.py
+++ b/lib/Python/Lib/beaker/crypto/__init__.py
diff --git a/module/lib/beaker/crypto/jcecrypto.py b/lib/Python/Lib/beaker/crypto/jcecrypto.py
index 4062d513e..4062d513e 100644
--- a/module/lib/beaker/crypto/jcecrypto.py
+++ b/lib/Python/Lib/beaker/crypto/jcecrypto.py
diff --git a/module/lib/beaker/crypto/pbkdf2.py b/lib/Python/Lib/beaker/crypto/pbkdf2.py
index 96dc5fbb2..96dc5fbb2 100644
--- a/module/lib/beaker/crypto/pbkdf2.py
+++ b/lib/Python/Lib/beaker/crypto/pbkdf2.py
diff --git a/module/lib/beaker/crypto/pycrypto.py b/lib/Python/Lib/beaker/crypto/pycrypto.py
index a3eb4d9db..a3eb4d9db 100644
--- a/module/lib/beaker/crypto/pycrypto.py
+++ b/lib/Python/Lib/beaker/crypto/pycrypto.py
diff --git a/module/lib/beaker/crypto/util.py b/lib/Python/Lib/beaker/crypto/util.py
index d97e8ce6f..d97e8ce6f 100644
--- a/module/lib/beaker/crypto/util.py
+++ b/lib/Python/Lib/beaker/crypto/util.py
diff --git a/module/lib/beaker/exceptions.py b/lib/Python/Lib/beaker/exceptions.py
index cc0eed286..cc0eed286 100644
--- a/module/lib/beaker/exceptions.py
+++ b/lib/Python/Lib/beaker/exceptions.py
diff --git a/module/lib/beaker/ext/__init__.py b/lib/Python/Lib/beaker/ext/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/lib/beaker/ext/__init__.py
+++ b/lib/Python/Lib/beaker/ext/__init__.py
diff --git a/module/lib/beaker/ext/database.py b/lib/Python/Lib/beaker/ext/database.py
index 701e6f7d2..701e6f7d2 100644
--- a/module/lib/beaker/ext/database.py
+++ b/lib/Python/Lib/beaker/ext/database.py
diff --git a/module/lib/beaker/ext/google.py b/lib/Python/Lib/beaker/ext/google.py
index dd8380d7f..dd8380d7f 100644
--- a/module/lib/beaker/ext/google.py
+++ b/lib/Python/Lib/beaker/ext/google.py
diff --git a/module/lib/beaker/ext/memcached.py b/lib/Python/Lib/beaker/ext/memcached.py
index 96516953f..96516953f 100644
--- a/module/lib/beaker/ext/memcached.py
+++ b/lib/Python/Lib/beaker/ext/memcached.py
diff --git a/module/lib/beaker/ext/sqla.py b/lib/Python/Lib/beaker/ext/sqla.py
index 8c79633c1..8c79633c1 100644
--- a/module/lib/beaker/ext/sqla.py
+++ b/lib/Python/Lib/beaker/ext/sqla.py
diff --git a/module/lib/beaker/middleware.py b/lib/Python/Lib/beaker/middleware.py
index 7ba88b37d..7ba88b37d 100644
--- a/module/lib/beaker/middleware.py
+++ b/lib/Python/Lib/beaker/middleware.py
diff --git a/module/lib/beaker/session.py b/lib/Python/Lib/beaker/session.py
index 7d465530b..7d465530b 100644
--- a/module/lib/beaker/session.py
+++ b/lib/Python/Lib/beaker/session.py
diff --git a/module/lib/beaker/synchronization.py b/lib/Python/Lib/beaker/synchronization.py
index 761303707..761303707 100644
--- a/module/lib/beaker/synchronization.py
+++ b/lib/Python/Lib/beaker/synchronization.py
diff --git a/module/lib/beaker/util.py b/lib/Python/Lib/beaker/util.py
index 04c9617c5..04c9617c5 100644
--- a/module/lib/beaker/util.py
+++ b/lib/Python/Lib/beaker/util.py
diff --git a/lib/Python/Lib/bitmath/__init__.py b/lib/Python/Lib/bitmath/__init__.py
new file mode 100644
index 000000000..1a8283655
--- /dev/null
+++ b/lib/Python/Lib/bitmath/__init__.py
@@ -0,0 +1,1261 @@
+# -*- coding: utf-8 -*-
+# The MIT License (MIT)
+#
+# Copyright © 2014 Tim Bielawa <timbielawa@gmail.com>
+# See GitHub Contributors Graph for more information
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sub-license, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""Reference material:
+
+Prefixes for binary multiples:
+http://physics.nist.gov/cuu/Units/binary.html
+
+decimal and binary prefixes:
+man 7 units (from the Linux Documentation Project 'man-pages' package)
+
+
+BEFORE YOU GET HASTY WITH EXCLUDING CODE FROM COVERAGE: If you
+absolutely need to skip code coverage because of a strange Python 2.x
+vs 3.x thing, use the fancy environment substitution stuff from the
+.coverage RC file. In review:
+
+* If you *NEED* to skip a statement because of Python 2.x issues add the following:
+
+ # pragma: PY2X no cover
+
+* If you *NEED* to skip a statement because of Python 3.x issues add the following:
+
+ # pragma: PY3X no cover
+
+In this configuration, statements which are skipped in 2.x are still
+covered in 3.x, and the reverse holds true for tests skipped in 3.x.
+"""
+
+from __future__ import print_function
+
+import argparse
+import contextlib
+import fnmatch
+import math
+import numbers
+import os
+import os.path
+import sys
+
+__all__ = ['Bit', 'Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB',
+ 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'Kib',
+ 'Mib', 'Gib', 'Tib', 'Pib', 'Eib', 'kb', 'Mb', 'Gb', 'Tb',
+ 'Pb', 'Eb', 'Zb', 'Yb', 'getsize', 'listdir', 'format',
+ 'format_string', 'format_plural', 'parse_string']
+
+# Python 3.x compat
+if sys.version > '3':
+ long = int # pragma: PY2X no cover
+
+# Constants for referring to prefix systems
+NIST = int(2)
+SI = int(10)
+
+SI_PREFIXES = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
+SI_STEPS = {
+ 'Bit': 1 / 8.0,
+ 'Byte': 1,
+ 'k': 1000,
+ 'M': 1000000,
+ 'G': 1000000000,
+ 'T': 1000000000000,
+ 'P': 1000000000000000,
+ 'E': 1000000000000000000,
+ 'Z': 1000000000000000000000,
+ 'Y': 1000000000000000000000000
+}
+
+NIST_PREFIXES = ['Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei']
+NIST_STEPS = {
+ 'Bit': 1 / 8.0,
+ 'Byte': 1,
+ 'Ki': 1024,
+ 'Mi': 1048576,
+ 'Gi': 1073741824,
+ 'Ti': 1099511627776,
+ 'Pi': 1125899906842624,
+ 'Ei': 1152921504606846976
+}
+
+# A list of all the valid prefix unit types. Mostly for reference,
+# also used by the CLI tool as valid types
+
+ALL_UNIT_TYPES = ['Bit', 'Byte', 'kb', 'kB', 'Mb', 'MB', 'Gb', 'GB', 'Tb',
+ 'TB', 'Pb', 'PB', 'Eb', 'EB', 'Zb', 'ZB', 'Yb',
+ 'YB', 'Kib', 'KiB', 'Mib', 'MiB', 'Gib', 'GiB',
+ 'Tib', 'TiB', 'Pib', 'PiB', 'Eib', 'EiB']
+
+######################################################################
+# Set up our module variables/constants
+
+###################################
+# Internal:
+
+# Console repr(), ex: MiB(13.37), or kB(42.0)
+_FORMAT_REPR = '{unit_singular}({value})'
+
+###################################
+# Exposed:
+
+# String representation, ex:13.37 MiB, or 42.0 kB
+format_string = "{value} {unit}"
+
+# Pluralization behavior
+format_plural = False
+
+
+######################################################################
+# Base class for everything else
+class Bitmath(object):
+ """The base class for all the other prefix classes"""
+
+ def __init__(self, value=0, bytes=None, bits=None):
+ """Instantiate with `value` by the unit, in plain bytes, or
+bits. Don't supply more than one keyword.
+
+default behavior: initialize with value of 0
+only setting value: assert bytes is None and bits is None
+only setting bytes: assert value == 0 and bits is None
+only setting bits: assert value == 0 and bytes is None
+ """
+ _raise = False
+ if (value == 0) and (bytes is None) and (bits is None):
+ pass
+ # Setting by bytes
+ elif bytes is not None:
+ if (value == 0) and (bits is None):
+ pass
+ else:
+ _raise = True
+ # setting by bits
+ elif bits is not None:
+ if (value == 0) and (bytes is None):
+ pass
+ else:
+ _raise = True
+
+ if _raise:
+ raise ValueError("Only one parameter of: value, bytes, or bits is allowed")
+
+ self._do_setup()
+ if bytes:
+ # We were provided with the fundamental base unit, no need
+ # to normalize
+ self._byte_value = bytes
+ self._bit_value = bytes * 8.0
+ elif bits:
+ # We were *ALMOST* given the fundamental base
+ # unit. Translate it into the fundamental unit then
+ # normalize.
+ self._byte_value = bits / 8.0
+ self._bit_value = bits
+ else:
+ # We were given a value representative of this *prefix
+ # unit*. We need to normalize it into the number of bytes
+ # it represents.
+ self._norm(value)
+
+ # We have the fundamental unit figured out. Set the 'pretty' unit
+ self._set_prefix_value()
+
+ def _set_prefix_value(self):
+ self.prefix_value = self._to_prefix_value(self._byte_value)
+
+ def _to_prefix_value(self, value):
+ """Return the number of bits/bytes as they would look like if we
+converted *to* this unit"""
+ return value / float(self._unit_value)
+
+ def _setup(self):
+ raise NotImplementedError("The base 'bitmath.Bitmath' class can not be used directly")
+
+ def _do_setup(self):
+ """Setup basic parameters for this class.
+
+`base` is the numeric base which when raised to `power` is equivalent
+to 1 unit of the corresponding prefix. I.e., base=2, power=10
+represents 2^10, which is the NIST Binary Prefix for 1 Kibibyte.
+
+Likewise, for the SI prefix classes `base` will be 10, and the `power`
+for the Kilobyte is 3.
+"""
+ (self._base, self._power, self._name_singular, self._name_plural) = self._setup()
+ self._unit_value = self._base ** self._power
+
+ def _norm(self, value):
+ """Normalize the input value into the fundamental unit for this prefix
+type"""
+ self._byte_value = value * self._unit_value
+ self._bit_value = self._byte_value * 8.0
+
+ @property
+ def base(self):
+ """Return the mathematical base of an instance"""
+ return self._base
+
+ @property
+ def binary(self):
+ """Returns the binary representation of an instance in binary 1s and
+0s. Note that for very large numbers this will mean a lot of 1s and
+0s. For example, GiB(100) would be represented as:
+
+0b1100100000000000000000000000000000000000
+
+That leading '0b' is normal. That's how Python represents binary."""
+ return bin(int(self.bits))
+
+ @property
+ def bin(self):
+ """Alias for instance.binary. Returns the binary representation of an
+instance in binary 1s and 0s."""
+ return self.binary
+
+ @property
+ def bits(self):
+ """Return the number of bits in an instance"""
+ return self._bit_value
+
+ @property
+ def bytes(self):
+ """Return the number of bytes in an instance"""
+ return self._byte_value
+
+ @property
+ def power(self):
+ """Return the mathematical power of an instance"""
+ return self._power
+
+ @property
+ def system(self):
+ """Return the system of units used to measure this instance"""
+ if self._base == 2:
+ return "NIST"
+ elif self._base == 10:
+ return "SI"
+ else:
+ # I don't expect to ever encounter this logic branch, but
+ # hey, it's better to have extra test coverage than
+ # insufficient test coverage.
+ raise ValueError("Instances mathematical base is an unsupported value: %s" % (
+ str(self._base)))
+
+ @property
+ def unit(self):
+ """Return the string that is this instances prefix unit name
+in agreement with this instance value (singular or plural). Following
+the convention that only 1 is singular. This method will always return
+the singular form when bitmath.format_plural is False (default value).
+
+For instance, when plural form is enabled, KiB(1).unit == 'KiB',
+Byte(0).unit == 'Bytes', Byte(1).unit == 'Byte', Byte(1.1).unit == 'Bytes'
+and Gb(2).unit == 'Gbs'"""
+ global format_plural
+
+ if self.prefix_value == 1:
+ # If it's a '1', return it singular, no matter what
+ return self._name_singular
+ elif format_plural:
+ # Pluralization requested
+ return self._name_plural
+ else:
+ # Pluralization NOT requested, and the value is not 1
+ return self._name_singular
+
+ @property
+ def unit_plural(self):
+ """Return the string that is this instances prefix unit name in the
+plural form.
+
+For instance, KiB(1).unit_plural == 'KiB', Byte(1024).unit_plural == 'Bytes',
+and Gb(1).unit_plural == 'Gb'"""
+ return self._name_plural
+
+ @property
+ def unit_singular(self):
+ """Return the string that is this instances prefix unit name in the
+singular form.
+
+For instance, KiB(1).unit_singular == 'KiB', Byte(1024).unit == 'B', and
+Gb(1).unit_singular == 'Gb'"""
+ return self._name_singular
+
+ @property
+ def value(self):
+ """Returns the "prefix" value of an instance"""
+ return self.prefix_value
+
+ @classmethod
+ def from_other(cls, item):
+ """Factory function to return instances of `item` converted in a new
+instance of `cls`. Because this is a class method, it may be called
+from any bitmath class object without the need to explicitly
+instantiate the class ahead of time.
+
+*Implicit Parameter:*
+- `cls` A bitmath class, implicitly set to the class of the class
+ object it is called on
+
+*User Supplied Parameter:*
+- `item` A bitmath class instance
+
+*Example:*
+>>> import bitmath
+>>> kib = bitmath.KiB.from_other(bitmath.MiB(1))
+>>> print kib
+KiB(1024.0)
+"""
+ if isinstance(item, Bitmath):
+ return cls(bits=item.bits)
+ else:
+ raise ValueError("The provided items must be a valid bitmath class: %s" %
+ str(item.__class__))
+
+ ######################################################################
+ # The following implement the Python datamodel customization methods
+ #
+ # Reference: http://docs.python.org/2.7/reference/datamodel.html#basic-customization
+
+ def __repr__(self):
+ """Representation of this object as you would expect to see in an
+interpreter"""
+ global _FORMAT_REPR
+ return self.format(_FORMAT_REPR)
+
+ def __str__(self):
+ """String representation of this object"""
+ global format_string
+ return self.format(format_string)
+
+ def format(self, fmt):
+ """Return a representation of this instance formatted with user
+supplied syntax"""
+ _fmt_params = {
+ 'base': self.base,
+ 'bin': self.bin,
+ 'binary': self.binary,
+ 'bits': self.bits,
+ 'bytes': self.bytes,
+ 'power': self.power,
+ 'system': self.system,
+ 'unit': self.unit,
+ 'unit_plural': self.unit_plural,
+ 'unit_singular': self.unit_singular,
+ 'value': self.value
+ }
+
+ return fmt.format(**_fmt_params)
+
+ ##################################################################
+ # Guess the best human-readable prefix unit for representation
+ ##################################################################
+
+ def best_prefix(self, system=None):
+ """Optional parameter, `system`, allows you to prefer NIST or SI in
+the results. By default, the current system is used (Bit/Byte default
+to NIST).
+
+Logic discussion/notes:
+
+Base-case, does it need converting?
+
+If the instance is less than one Byte, return the instance as a Bit
+instance.
+
+Else, begin by recording the unit system the instance is defined
+by. This determines which steps (NIST_STEPS/SI_STEPS) we iterate over.
+
+If the instance is not already a ``Byte`` instance, convert it to one.
+
+NIST units step up by powers of 1024, SI units step up by powers of
+1000.
+
+Take integer value of the log(base=STEP_POWER) of the instance's byte
+value. E.g.:
+
+ >>> int(math.log(Gb(100).bytes, 1000))
+ 3
+
+This will return a value >= 0. The following determines the 'best
+prefix unit' for representation:
+
+* result == 0, best represented as a Byte
+* result >= len(SYSTEM_STEPS), best represented as an Exbi/Exabyte
+* 0 < result < len(SYSTEM_STEPS), best represented as SYSTEM_PREFIXES[result-1]
+
+ """
+ if self < Byte(1):
+ return Bit.from_other(self)
+ else:
+ if not type(self) == Byte:
+ _inst = Byte.from_other(self)
+ else:
+ _inst = self
+
+ # Which table to consult? Was a preferred system provided?
+ if system is None:
+ # No preference. Use existing system
+ if self.system == 'NIST':
+ _STEPS = NIST_PREFIXES
+ _BASE = 1024
+ elif self.system == 'SI':
+ _STEPS = SI_PREFIXES
+ _BASE = 1000
+ # Anything else would have raised by now
+ else:
+ # Preferred system provided.
+ if system == NIST:
+ _STEPS = NIST_PREFIXES
+ _BASE = 1024
+ elif system == SI:
+ _STEPS = SI_PREFIXES
+ _BASE = 1000
+ else:
+ raise ValueError("Invalid value given for 'system' parameter."
+ " Must be one of NIST or SI")
+
+ # Index of the string of the best prefix in the STEPS list
+ _index = int(math.log(_inst.bytes, _BASE))
+
+ # Recall that the log() function returns >= 0. This doesn't
+ # map to the STEPS list 1:1. That is to say, 0 is handled with
+ # special care. So if the _index is 1, we actually want item 0
+ # in the list.
+
+ if _index == 0:
+ # Already a Byte() type, so return it.
+ return _inst
+ elif _index >= len(_STEPS):
+ # This is a really big number. Use the biggest prefix we've got
+ _best_prefix = _STEPS[-1]
+ elif 0 < _index < len(_STEPS):
+ # There is an appropriate prefix unit to represent this
+ _best_prefix = _STEPS[_index - 1]
+
+ _conversion_method = getattr(
+ self,
+ 'to_%sB' % _best_prefix)
+
+ return _conversion_method()
+
+ ##################################################################
+
+ def to_Bit(self):
+ return Bit(self._bit_value)
+
+ def to_Byte(self):
+ return Byte(self._byte_value / float(NIST_STEPS['Byte']))
+
+ # Properties
+ Bit = property(lambda s: s.to_Bit())
+ Byte = property(lambda s: s.to_Byte())
+
+ ##################################################################
+
+ def to_KiB(self):
+ return KiB(bits=self._bit_value)
+
+ def to_Kib(self):
+ return Kib(bits=self._bit_value)
+
+ def to_kB(self):
+ return kB(bits=self._bit_value)
+
+ def to_kb(self):
+ return kb(bits=self._bit_value)
+
+ # Properties
+ KiB = property(lambda s: s.to_KiB())
+ Kib = property(lambda s: s.to_Kib())
+ kB = property(lambda s: s.to_kB())
+ kb = property(lambda s: s.to_kb())
+
+ ##################################################################
+
+ def to_MiB(self):
+ return MiB(bits=self._bit_value)
+
+ def to_Mib(self):
+ return Mib(bits=self._bit_value)
+
+ def to_MB(self):
+ return MB(bits=self._bit_value)
+
+ def to_Mb(self):
+ return Mb(bits=self._bit_value)
+
+ # Properties
+ MiB = property(lambda s: s.to_MiB())
+ Mib = property(lambda s: s.to_Mib())
+ MB = property(lambda s: s.to_MB())
+ Mb = property(lambda s: s.to_Mb())
+
+ ##################################################################
+
+ def to_GiB(self):
+ return GiB(bits=self._bit_value)
+
+ def to_Gib(self):
+ return Gib(bits=self._bit_value)
+
+ def to_GB(self):
+ return GB(bits=self._bit_value)
+
+ def to_Gb(self):
+ return Gb(bits=self._bit_value)
+
+ # Properties
+ GiB = property(lambda s: s.to_GiB())
+ Gib = property(lambda s: s.to_Gib())
+ GB = property(lambda s: s.to_GB())
+ Gb = property(lambda s: s.to_Gb())
+
+ ##################################################################
+
+ def to_TiB(self):
+ return TiB(bits=self._bit_value)
+
+ def to_Tib(self):
+ return Tib(bits=self._bit_value)
+
+ def to_TB(self):
+ return TB(bits=self._bit_value)
+
+ def to_Tb(self):
+ return Tb(bits=self._bit_value)
+
+ # Properties
+ TiB = property(lambda s: s.to_TiB())
+ Tib = property(lambda s: s.to_Tib())
+ TB = property(lambda s: s.to_TB())
+ Tb = property(lambda s: s.to_Tb())
+
+ ##################################################################
+
+ def to_PiB(self):
+ return PiB(bits=self._bit_value)
+
+ def to_Pib(self):
+ return Pib(bits=self._bit_value)
+
+ def to_PB(self):
+ return PB(bits=self._bit_value)
+
+ def to_Pb(self):
+ return Pb(bits=self._bit_value)
+
+ # Properties
+ PiB = property(lambda s: s.to_PiB())
+ Pib = property(lambda s: s.to_Pib())
+ PB = property(lambda s: s.to_PB())
+ Pb = property(lambda s: s.to_Pb())
+
+ ##################################################################
+
+ def to_EiB(self):
+ return EiB(bits=self._bit_value)
+
+ def to_Eib(self):
+ return Eib(bits=self._bit_value)
+
+ def to_EB(self):
+ return EB(bits=self._bit_value)
+
+ def to_Eb(self):
+ return Eb(bits=self._bit_value)
+
+ # Properties
+ EiB = property(lambda s: s.to_EiB())
+ Eib = property(lambda s: s.to_Eib())
+ EB = property(lambda s: s.to_EB())
+ Eb = property(lambda s: s.to_Eb())
+
+ ##################################################################
+ # The SI units go beyond the NIST units. They also have the Zetta
+ # and Yotta prefixes.
+
+ def to_ZB(self):
+ return ZB(bits=self._bit_value)
+
+ def to_Zb(self):
+ return Zb(bits=self._bit_value)
+
+ # Properties
+ ZB = property(lambda s: s.to_ZB())
+ Zb = property(lambda s: s.to_Zb())
+
+ ##################################################################
+
+ def to_YB(self):
+ return YB(bits=self._bit_value)
+
+ def to_Yb(self):
+ return Yb(bits=self._bit_value)
+
+ # Properties
+ YB = property(lambda s: s.to_YB())
+ Yb = property(lambda s: s.to_Yb())
+
+ ##################################################################
+ # Rich comparison operations
+ ##################################################################
+
+ def __lt__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value < other
+ else:
+ return self._byte_value < other.bytes
+
+ def __le__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value <= other
+ else:
+ return self._byte_value <= other.bytes
+
+ def __eq__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value == other
+ else:
+ return self._byte_value == other.bytes
+
+ def __ne__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value != other
+ else:
+ return self._byte_value != other.bytes
+
+ def __gt__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value > other
+ else:
+ return self._byte_value > other.bytes
+
+ def __ge__(self, other):
+ if isinstance(other, numbers.Number):
+ return self.prefix_value >= other
+ else:
+ return self._byte_value >= other.bytes
+
+ ##################################################################
+ # Basic math operations
+ ##################################################################
+
+ # Reference: http://docs.python.org/2.7/reference/datamodel.html#emulating-numeric-types
+
+ """These methods are called to implement the binary arithmetic
+operations (+, -, *, //, %, divmod(), pow(), **, <<, >>, &, ^, |). For
+instance, to evaluate the expression x + y, where x is an instance of
+a class that has an __add__() method, x.__add__(y) is called. The
+__divmod__() method should be the equivalent to using __floordiv__()
+and __mod__(); it should not be related to __truediv__() (described
+below). Note that __pow__() should be defined to accept an optional
+third argument if the ternary version of the built-in pow() function
+is to be supported.object.__complex__(self)
+"""
+
+ def __add__(self, other):
+ """Supported operations with result types:
+
+- bm + bm = bm
+- bm + num = num
+- num + bm = num (see radd)
+"""
+ if isinstance(other, numbers.Number):
+ # bm + num
+ return other + self.value
+ else:
+ # bm + bm
+ total_bytes = self._byte_value + other.bytes
+ return (type(self))(bytes=total_bytes)
+
+ def __sub__(self, other):
+ """Subtraction: Supported operations with result types:
+
+- bm - bm = bm
+- bm - num = num
+- num - bm = num (see rsub)
+"""
+ if isinstance(other, numbers.Number):
+ # bm - num
+ return self.value - other
+ else:
+ # bm - bm
+ total_bytes = self._byte_value - other.bytes
+ return (type(self))(bytes=total_bytes)
+
+ def __mul__(self, other):
+ """Multiplication: Supported operations with result types:
+
+- bm1 * bm2 = bm1
+- bm * num = bm
+- num * bm = num (see rmul)
+"""
+ if isinstance(other, numbers.Number):
+ # bm * num
+ result = self._byte_value * other
+ return (type(self))(bytes=result)
+ else:
+ # bm1 * bm2
+ _other = other.value * other.base ** other.power
+ _self = self.prefix_value * self._base ** self._power
+ return (type(self))(bytes=_other * _self)
+
+ """The division operator (/) is implemented by these methods. The
+__truediv__() method is used when __future__.division is in effect,
+otherwise __div__() is used. If only one of these two methods is
+defined, the object will not support division in the alternate
+context; TypeError will be raised instead."""
+
+ def __div__(self, other):
+ """Division: Supported operations with result types:
+
+- bm1 / bm2 = num
+- bm / num = bm
+- num / bm = num (see rdiv)
+"""
+ if isinstance(other, numbers.Number):
+ # bm / num
+ result = self._byte_value / other
+ return (type(self))(bytes=result)
+ else:
+ # bm1 / bm2
+ return self._byte_value / float(other.bytes)
+
+ def __truediv__(self, other):
+ # num / bm
+ return self.__div__(other)
+
+ # def __floordiv__(self, other):
+ # return NotImplemented
+
+ # def __mod__(self, other):
+ # return NotImplemented
+
+ # def __divmod__(self, other):
+ # return NotImplemented
+
+ # def __pow__(self, other, modulo=None):
+ # return NotImplemented
+
+ ##################################################################
+
+ """These methods are called to implement the binary arithmetic
+operations (+, -, *, /, %, divmod(), pow(), **, <<, >>, &, ^, |) with
+reflected (swapped) operands. These functions are only called if the
+left operand does not support the corresponding operation and the
+operands are of different types. [2] For instance, to evaluate the
+expression x - y, where y is an instance of a class that has an
+__rsub__() method, y.__rsub__(x) is called if x.__sub__(y) returns
+NotImplemented.
+
+These are the add/sub/mul/div methods for syntax where a number type
+is given for the LTYPE and a bitmath object is given for the
+RTYPE. E.g., 3 * MiB(3), or 10 / GB(42)
+"""
+
+ def __radd__(self, other):
+ # num + bm = num
+ return other + self.value
+
+ def __rsub__(self, other):
+ # num - bm = num
+ return other - self.value
+
+ def __rmul__(self, other):
+ # num * bm = bm
+ return self * other
+
+ def __rdiv__(self, other):
+ # num / bm = num
+ return other / float(self.value)
+
+ def __rtruediv__(self, other):
+ # num / bm = num
+ return other / float(self.value)
+
+ """Called to implement the built-in functions complex(), int(),
+long(), and float(). Should return a value of the appropriate type.
+
+If one of those methods does not support the operation with the
+supplied arguments, it should return NotImplemented.
+
+For bitmath purposes, these methods return the int/long/float
+equivalent of the this instances prefix Unix value. That is to say:
+
+ - int(KiB(3.336)) would return 3
+ - long(KiB(3.336)) would return 3L
+ - float(KiB(3.336)) would return 3.336
+"""
+
+ def __int__(self):
+ """Return this instances prefix unit as an integer"""
+ return int(self.prefix_value)
+
+ def __long__(self):
+ """Return this instances prefix unit as a long integer"""
+ return long(self.prefix_value) # pragma: PY3X no cover
+
+ def __float__(self):
+ """Return this instances prefix unit as a floating point number"""
+ return float(self.prefix_value)
+
+ ##################################################################
+ # Bitwise operations
+ ##################################################################
+
+ def __lshift__(self, other):
+ """Left shift, ex: 100 << 2
+
+A left shift by n bits is equivalent to multiplication by pow(2,
+n). A long integer is returned if the result exceeds the range of
+plain integers."""
+ shifted = int(self.bits) << other
+ return type(self)(bits=shifted)
+
+ def __rshift__(self, other):
+ """Right shift, ex: 100 >> 2
+
+A right shift by n bits is equivalent to division by pow(2, n)."""
+ shifted = int(self.bits) >> other
+ return type(self)(bits=shifted)
+
+ def __and__(self, other):
+ """"Bitwise and, ex: 100 & 2
+
+bitwise and". Each bit of the output is 1 if the corresponding bit
+of x AND of y is 1, otherwise it's 0."""
+ andd = int(self.bits) & other
+ return type(self)(bits=andd)
+
+ def __xor__(self, other):
+ """Bitwise xor, ex: 100 ^ 2
+
+Does a "bitwise exclusive or". Each bit of the output is the same
+as the corresponding bit in x if that bit in y is 0, and it's the
+complement of the bit in x if that bit in y is 1."""
+ xord = int(self.bits) ^ other
+ return type(self)(bits=xord)
+
+ def __or__(self, other):
+ """Bitwise or, ex: 100 | 2
+
+Does a "bitwise or". Each bit of the output is 0 if the corresponding
+bit of x AND of y is 0, otherwise it's 1."""
+ ord = int(self.bits) | other
+ return type(self)(bits=ord)
+
+ ##################################################################
+
+ def __neg__(self):
+ """The negative version of this instance"""
+ return (type(self))(-abs(self.prefix_value))
+
+ def __pos__(self):
+ return (type(self))(abs(self.prefix_value))
+
+ def __abs__(self):
+ return (type(self))(abs(self.prefix_value))
+
+ # def __invert__(self):
+ # """Called to implement the unary arithmetic operations (-, +, abs()
+ # and ~)."""
+ # return NotImplemented
+
+
+######################################################################
+# First, the bytes...
+
+class Byte(Bitmath):
+ """Byte based types fundamentally operate on self._bit_value"""
+ def _setup(self):
+ return (2, 0, 'Byte', 'Bytes')
+
+######################################################################
+# NIST Prefixes for Byte based types
+
+
+class KiB(Byte):
+ def _setup(self):
+ return (2, 10, 'KiB', 'KiBs')
+
+
+class MiB(Byte):
+ def _setup(self):
+ return (2, 20, 'MiB', 'MiBs')
+
+
+class GiB(Byte):
+ def _setup(self):
+ return (2, 30, 'GiB', 'GiBs')
+
+
+class TiB(Byte):
+ def _setup(self):
+ return (2, 40, 'TiB', 'TiBs')
+
+
+class PiB(Byte):
+ def _setup(self):
+ return (2, 50, 'PiB', 'PiBs')
+
+
+class EiB(Byte):
+ def _setup(self):
+ return (2, 60, 'EiB', 'EiBs')
+
+
+######################################################################
+# SI Prefixes for Byte based types
+class kB(Byte):
+ def _setup(self):
+ return (10, 3, 'kB', 'kBs')
+
+
+class MB(Byte):
+ def _setup(self):
+ return (10, 6, 'MB', 'MBs')
+
+
+class GB(Byte):
+ def _setup(self):
+ return (10, 9, 'GB', 'GBs')
+
+
+class TB(Byte):
+ def _setup(self):
+ return (10, 12, 'TB', 'TBs')
+
+
+class PB(Byte):
+ def _setup(self):
+ return (10, 15, 'PB', 'PBs')
+
+
+class EB(Byte):
+ def _setup(self):
+ return (10, 18, 'EB', 'EBs')
+
+
+class ZB(Byte):
+ def _setup(self):
+ return (10, 21, 'ZB', 'ZBs')
+
+
+class YB(Byte):
+ def _setup(self):
+ return (10, 24, 'YB', 'YBs')
+
+
+######################################################################
+# And now the bit types
+class Bit(Bitmath):
+ """Bit based types fundamentally operate on self._bit_value"""
+
+ def _set_prefix_value(self):
+ self.prefix_value = self._to_prefix_value(self._bit_value)
+
+ def _setup(self):
+ return (2, 0, 'Bit', 'Bits')
+
+ def _norm(self, value):
+ """Normalize the input value into the fundamental unit for this prefix
+type"""
+ self._bit_value = value * self._unit_value
+ self._byte_value = self._bit_value / 8.0
+
+
+######################################################################
+# NIST Prefixes for Bit based types
+class Kib(Bit):
+ def _setup(self):
+ return (2, 10, 'Kib', 'Kibs')
+
+
+class Mib(Bit):
+ def _setup(self):
+ return (2, 20, 'Mib', 'Mibs')
+
+
+class Gib(Bit):
+ def _setup(self):
+ return (2, 30, 'Gib', 'Gibs')
+
+
+class Tib(Bit):
+ def _setup(self):
+ return (2, 40, 'Tib', 'Tibs')
+
+
+class Pib(Bit):
+ def _setup(self):
+ return (2, 50, 'Pib', 'Pibs')
+
+
+class Eib(Bit):
+ def _setup(self):
+ return (2, 60, 'Eib', 'Eibs')
+
+
+######################################################################
+# SI Prefixes for Bit based types
+class kb(Bit):
+ def _setup(self):
+ return (10, 3, 'kb', 'kbs')
+
+
+class Mb(Bit):
+ def _setup(self):
+ return (10, 6, 'Mb', 'Mbs')
+
+
+class Gb(Bit):
+ def _setup(self):
+ return (10, 9, 'Gb', 'Gbs')
+
+
+class Tb(Bit):
+ def _setup(self):
+ return (10, 12, 'Tb', 'Tbs')
+
+
+class Pb(Bit):
+ def _setup(self):
+ return (10, 15, 'Pb', 'Pbs')
+
+
+class Eb(Bit):
+ def _setup(self):
+ return (10, 18, 'Eb', 'Ebs')
+
+
+class Zb(Bit):
+ def _setup(self):
+ return (10, 21, 'Zb', 'Zbs')
+
+
+class Yb(Bit):
+ def _setup(self):
+ return (10, 24, 'Yb', 'Ybs')
+
+
+######################################################################
+# Utility functions
+def getsize(path, bestprefix=True, system=NIST):
+ """Return a bitmath instance in the best human-readable representation
+of the file size at `path`. Optionally, provide a preferred unit
+system by setting `system` to either `bitmath.NIST` (default) or
+`bitmath.SI`.
+
+Optionally, set ``bestprefix`` to ``False`` to get ``bitmath.Byte``
+instances back.
+ """
+ _path = os.path.realpath(path)
+ size_bytes = os.path.getsize(_path)
+ if bestprefix:
+ return Byte(size_bytes).best_prefix(system=system)
+ else:
+ return Byte(size_bytes)
+
+
+def listdir(search_base, followlinks=False, filter='*',
+ relpath=False, bestprefix=False, system=NIST):
+ """This is a generator which recurses the directory tree
+`search_base`, yielding 2-tuples of:
+
+* The absolute/relative path to a discovered file
+* A bitmath instance representing the "apparent size" of the file.
+
+ - `search_base` - The directory to begin walking down.
+ - `followlinks` - Whether or not to follow symbolic links to directories
+ - `filter` - A glob (see :py:mod:`fnmatch`) to filter results with
+ (default: ``*``, everything)
+ - `relpath` - ``True`` to return the relative path from `pwd` or
+ ``False`` (default) to return the fully qualified path
+ - ``bestprefix`` - set to ``False`` to get ``bitmath.Byte``
+ instances back instead.
+ - `system` - Provide a preferred unit system by setting `system`
+ to either ``bitmath.NIST`` (default) or ``bitmath.SI``.
+
+.. note:: This function does NOT return tuples for directory entities.
+
+.. note:: Symlinks to **files** are followed automatically
+
+ """
+ for root, dirs, files in os.walk(search_base, followlinks=followlinks):
+ for name in fnmatch.filter(files, filter):
+ _path = os.path.join(root, name)
+ if relpath:
+ # RELATIVE path
+ _return_path = os.path.relpath(_path, '.')
+ else:
+ # REAL path
+ _return_path = os.path.realpath(_path)
+
+ if followlinks:
+ yield (_return_path, getsize(_path, bestprefix=bestprefix, system=system))
+ else:
+ if os.path.isdir(_path) or os.path.islink(_path):
+ pass
+ else:
+ yield (_return_path, getsize(_path, bestprefix=bestprefix, system=system))
+
+
+def parse_string(s):
+ """Parse a string with units and try to make a bitmath object out of
+it.
+
+String inputs may include whitespace characters between the value and
+the unit.
+ """
+ # Strings only please
+ if type(s) is not str:
+ raise ValueError("parse_string only accepts string inputs but a %s was given" %
+ type(s))
+
+ # get the index of the first alphabetic character
+ try:
+ index = list(map(str.isalpha, s)).index(True)
+ except ValueError:
+ # If there's no alphabetic characters we won't be able to .index(True)
+ raise ValueError("No unit detected, can not parse string '%s' into a bitmath object" % s)
+
+ # split the string into the value and the unit
+ val, unit = s[:index], s[index:]
+
+ # see if the unit exists as a type in our namespace
+
+ if unit == "b":
+ unit_class = Bit
+ elif unit == "B":
+ unit_class = Byte
+ else:
+ if not (hasattr(sys.modules[__name__], unit)
+ and isinstance(getattr(sys.modules[__name__], unit), type)):
+ raise ValueError("The unit %s is not a valid bitmath unit" % unit)
+ unit_class = globals()[unit]
+
+ try:
+ val = float(val)
+ except ValueError:
+ raise
+ try:
+ return unit_class(val)
+ except: # pragma: no cover
+ raise ValueError("Can't parse string %s into a bitmath object" % s)
+
+
+######################################################################
+# Contxt Managers
+@contextlib.contextmanager
+def format(fmt_str=None, plural=False, bestprefix=False):
+ """Context manager for printing bitmath instances.
+
+``fmt_str`` - a formatting mini-language compat formatting string. See
+the @properties (above) for a list of available items.
+
+``plural`` - True enables printing instances with 's's if they're
+plural. False (default) prints them as singular (no trailing 's').
+
+``bestprefix`` - True enables printing instances in their best
+human-readable representation. False, the default, prints instances
+using their current prefix unit.
+ """
+ if 'bitmath' not in globals():
+ import bitmath
+
+ if plural:
+ orig_fmt_plural = bitmath.format_plural
+ bitmath.format_plural = True
+
+ if fmt_str:
+ orig_fmt_str = bitmath.format_string
+ bitmath.format_string = fmt_str
+
+ yield
+
+ if plural:
+ bitmath.format_plural = orig_fmt_plural
+
+ if fmt_str:
+ bitmath.format_string = orig_fmt_str
+
+
+def cli_script_main(cli_args):
+ """
+ A command line interface to basic bitmath operations.
+ """
+ choices = ALL_UNIT_TYPES
+
+ parser = argparse.ArgumentParser(
+ description='Converts from one type of size to another.')
+ parser.add_argument('--from-stdin', default=False, action='store_true',
+ help='Reads number from stdin rather than the cli')
+ parser.add_argument(
+ '-f', '--from', choices=choices, nargs=1,
+ type=str, dest='fromunit', default=['Byte'],
+ help='Input type you are converting from. Defaultes to Byte.')
+ parser.add_argument(
+ '-t', '--to', choices=choices, required=False, nargs=1, type=str,
+ help=('Input type you are converting to. '
+ 'Attempts to detect best result if omitted.'), dest='tounit')
+ parser.add_argument(
+ 'size', nargs='*', type=float,
+ help='The number to convert.')
+
+ args = parser.parse_args(cli_args)
+
+ # Not sure how to cover this with tests, or if the functionality
+ # will remain in this form long enough for it to make writing a
+ # test worth the effort.
+ if args.from_stdin: # pragma: no cover
+ args.size = [float(sys.stdin.readline()[:-1])]
+
+ results = []
+
+ for size in args.size:
+ instance = getattr(__import__(
+ 'bitmath', fromlist=['True']), args.fromunit[0])(size)
+
+ # If we have a unit provided then use it
+ if args.tounit:
+ result = getattr(instance, args.tounit[0])
+ # Otherwise use the best_prefix call
+ else:
+ result = instance.best_prefix()
+
+ results.append(result)
+
+ return results
+
+
+def cli_script(): # pragma: no cover
+ # Wrapper around cli_script_main so we can unittest the command
+ # line functionality
+ for result in cli_script_main(sys.argv[1:]):
+ print(result)
+
+if __name__ == '__main__':
+ cli_script()
diff --git a/lib/Python/Lib/bitmath/integrations.py b/lib/Python/Lib/bitmath/integrations.py
new file mode 100644
index 000000000..e598ea1e9
--- /dev/null
+++ b/lib/Python/Lib/bitmath/integrations.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# The MIT License (MIT)
+#
+# Copyright © 2014 Tim Bielawa <timbielawa@gmail.com>
+# See GitHub Contributors Graph for more information
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sub-license, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import bitmath
+import argparse
+import progressbar.widgets
+
+######################################################################
+# Integrations with 3rd party modules
+def BitmathType(bmstring):
+ """An 'argument type' for integrations with the argparse module.
+
+For more information, see
+https://docs.python.org/2/library/argparse.html#type Of particular
+interest to us is this bit:
+
+ ``type=`` can take any callable that takes a single string
+ argument and returns the converted value
+
+I.e., ``type`` can be a function (such as this function) or a class
+which implements the ``__call__`` method.
+
+Example usage of the bitmath.BitmathType argparser type:
+
+ >>> import bitmath
+ >>> import argparse
+ >>> parser = argparse.ArgumentParser()
+ >>> parser.add_argument("--file-size", type=bitmath.BitmathType)
+ >>> parser.parse_args("--file-size 1337MiB".split())
+ Namespace(file_size=MiB(1337.0))
+
+Invalid usage includes any input that the bitmath.parse_string
+function already rejects. Additionally, **UNQUOTED** arguments with
+spaces in them are rejected (shlex.split used in the following
+examples to conserve single quotes in the parse_args call):
+
+ >>> parser = argparse.ArgumentParser()
+ >>> parser.add_argument("--file-size", type=bitmath.BitmathType)
+ >>> import shlex
+
+ >>> # The following is ACCEPTABLE USAGE:
+ ...
+ >>> parser.parse_args(shlex.split("--file-size '1337 MiB'"))
+ Namespace(file_size=MiB(1337.0))
+
+ >>> # The following is INCORRECT USAGE because the string "1337 MiB" is not quoted!
+ ...
+ >>> parser.parse_args(shlex.split("--file-size 1337 MiB"))
+ error: argument --file-size: 1337 can not be parsed into a valid bitmath object
+"""
+ try:
+ argvalue = bitmath.parse_string(bmstring)
+ except ValueError:
+ raise argparse.ArgumentTypeError("'%s' can not be parsed into a valid bitmath object" %
+ bmstring)
+ else:
+ return argvalue
+
+######################################################################
+# Speed widget for integration with the Progress bar module
+class BitmathFileTransferSpeed(progressbar.widgets.Widget):
+ """Widget for showing the transfer speed (useful for file transfers)."""
+ __slots__ = ('system', 'format')
+
+ def __init__(self, system=bitmath.NIST, format="{value:.2f} {unit}/s"):
+ self.system = system
+ self.format = format
+
+ def update(self, pbar):
+ """Updates the widget with the current NIST/SI speed.
+
+Basically, this calculates the average rate of update and figures out
+how to make a "pretty" prefix unit"""
+
+ if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6:
+ scaled = bitmath.Byte()
+ else:
+ speed = pbar.currval / pbar.seconds_elapsed
+ scaled = bitmath.Byte(speed).best_prefix(system=self.system)
+
+ return scaled.format(self.format)
diff --git a/module/lib/bottle.py b/lib/Python/Lib/bottle.py
index 2c243278e..2c243278e 100644
--- a/module/lib/bottle.py
+++ b/lib/Python/Lib/bottle.py
diff --git a/lib/Python/Lib/colorama/__init__.py b/lib/Python/Lib/colorama/__init__.py
new file mode 100644
index 000000000..4af0c1e11
--- /dev/null
+++ b/lib/Python/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, Cursor
+from .ansitowin32 import AnsiToWin32
+
+__version__ = '0.3.3'
+
diff --git a/lib/Python/Lib/colorama/ansi.py b/lib/Python/Lib/colorama/ansi.py
new file mode 100644
index 000000000..1cc722500
--- /dev/null
+++ b/lib/Python/Lib/colorama/ansi.py
@@ -0,0 +1,99 @@
+# 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['
+OSC = '\033]'
+BEL = '\007'
+
+
+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 AnsiCursor(object):
+ def UP(self, n=1):
+ return CSI + str(n) + "A"
+ def DOWN(self, n=1):
+ return CSI + str(n) + "B"
+ def FORWARD(self, n=1):
+ return CSI + str(n) + "C"
+ def BACK(self, n=1):
+ return CSI + str(n) + "D"
+ def POS(self, x=1, y=1):
+ return CSI + str(y) + ";" + str(x) + "H"
+
+def set_title(title):
+ return OSC + "2;" + title + BEL
+
+def clear_screen(mode=2):
+ return CSI + str(mode) + "J"
+
+def clear_line(mode=2):
+ return CSI + str(mode) + "K"
+
+
+class AnsiFore:
+ BLACK = 30
+ RED = 31
+ GREEN = 32
+ YELLOW = 33
+ BLUE = 34
+ MAGENTA = 35
+ CYAN = 36
+ WHITE = 37
+ RESET = 39
+
+ # These are fairly well supported, but not part of the standard.
+ LIGHTBLACK_EX = 90
+ LIGHTRED_EX = 91
+ LIGHTGREEN_EX = 92
+ LIGHTYELLOW_EX = 93
+ LIGHTBLUE_EX = 94
+ LIGHTMAGENTA_EX = 95
+ LIGHTCYAN_EX = 96
+ LIGHTWHITE_EX = 97
+
+
+class AnsiBack:
+ BLACK = 40
+ RED = 41
+ GREEN = 42
+ YELLOW = 43
+ BLUE = 44
+ MAGENTA = 45
+ CYAN = 46
+ WHITE = 47
+ RESET = 49
+
+ # These are fairly well supported, but not part of the standard.
+ LIGHTBLACK_EX = 100
+ LIGHTRED_EX = 101
+ LIGHTGREEN_EX = 102
+ LIGHTYELLOW_EX = 103
+ LIGHTBLUE_EX = 104
+ LIGHTMAGENTA_EX = 105
+ LIGHTCYAN_EX = 106
+ LIGHTWHITE_EX = 107
+
+
+class AnsiStyle:
+ BRIGHT = 1
+ DIM = 2
+ NORMAL = 22
+ RESET_ALL = 0
+
+Fore = AnsiCodes( AnsiFore )
+Back = AnsiCodes( AnsiBack )
+Style = AnsiCodes( AnsiStyle )
+Cursor = AnsiCursor()
diff --git a/lib/Python/Lib/colorama/ansitowin32.py b/lib/Python/Lib/colorama/ansitowin32.py
new file mode 100644
index 000000000..62e770c86
--- /dev/null
+++ b/lib/Python/Lib/colorama/ansitowin32.py
@@ -0,0 +1,228 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import re
+import sys
+import os
+
+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_CSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') # Control Sequence Introducer
+ ANSI_OSC_RE = re.compile('\033\]((?:.|;)*?)(\x07)') # Operating System Command
+
+ 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 = os.name == 'nt'
+ on_emulated_windows = on_windows and 'TERM' in os.environ
+
+ # should we strip ANSI sequences from our output?
+ if strip is None:
+ strip = on_windows and not on_emulated_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 not on_emulated_windows 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, ),
+ AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
+ AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
+ AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
+ AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
+ AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
+ AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
+ AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
+ AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
+ 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, ),
+ AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
+ AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
+ AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
+ AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
+ AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
+ AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
+ AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
+ AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
+ }
+ 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
+ text = self.convert_osc(text)
+ for match in self.ANSI_CSI_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(command, paramstring)
+ self.call_win32(command, params)
+
+
+ def extract_params(self, command, paramstring):
+ if command in 'Hf':
+ params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
+ while len(params) < 2:
+ # defaults:
+ params = params + (1,)
+ else:
+ params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
+ if len(params) == 0:
+ # defaults:
+ if command in 'JKm':
+ params = (0,)
+ elif command in 'ABCD':
+ params = (1,)
+
+ return params
+
+
+ def call_win32(self, command, params):
+ 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 'J':
+ winterm.erase_screen(params[0], on_stderr=self.on_stderr)
+ elif command in 'K':
+ winterm.erase_line(params[0], on_stderr=self.on_stderr)
+ elif command in 'Hf': # cursor position - absolute
+ winterm.set_cursor_position(params, on_stderr=self.on_stderr)
+ elif command in 'ABCD': # cursor position - relative
+ n = params[0]
+ # A - up, B - down, C - forward, D - back
+ x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
+ winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)
+
+
+ def convert_osc(self, text):
+ for match in self.ANSI_OSC_RE.finditer(text):
+ start, end = match.span()
+ text = text[:start] + text[end:]
+ paramstring, command = match.groups()
+ if command in '\x07': # \x07 = BEL
+ params = paramstring.split(";")
+ # 0 - change title and icon (we will only change title)
+ # 1 - change icon (we don't support this)
+ # 2 - change title
+ if params[0] in '02':
+ winterm.set_title(params[1])
+ return text
diff --git a/lib/Python/Lib/colorama/initialise.py b/lib/Python/Lib/colorama/initialise.py
new file mode 100644
index 000000000..7e27f84f8
--- /dev/null
+++ b/lib/Python/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/lib/Python/Lib/colorama/win32.py b/lib/Python/Lib/colorama/win32.py
new file mode 100644
index 000000000..c604f3721
--- /dev/null
+++ b/lib/Python/Lib/colorama/win32.py
@@ -0,0 +1,146 @@
+# 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, POINTER
+
+ COORD = wintypes._COORD
+
+ class CONSOLE_SCREEN_BUFFER_INFO(Structure):
+ """struct in wincon.h."""
+ _fields_ = [
+ ("dwSize", COORD),
+ ("dwCursorPosition", COORD),
+ ("wAttributes", wintypes.WORD),
+ ("srWindow", wintypes.SMALL_RECT),
+ ("dwMaximumWindowSize", 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,
+ COORD,
+ ]
+ _SetConsoleCursorPosition.restype = wintypes.BOOL
+
+ _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
+ _FillConsoleOutputCharacterA.argtypes = [
+ wintypes.HANDLE,
+ c_char,
+ wintypes.DWORD,
+ COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputCharacterA.restype = wintypes.BOOL
+
+ _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
+ _FillConsoleOutputAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ wintypes.DWORD,
+ COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputAttribute.restype = wintypes.BOOL
+
+ _SetConsoleTitleW = windll.kernel32.SetConsoleTitleA
+ _SetConsoleTitleW.argtypes = [
+ wintypes.LPCSTR
+ ]
+ _SetConsoleTitleW.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, adjust=True):
+ position = 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 = COORD(position.Y - 1, position.X - 1)
+ if adjust:
+ # 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.encode())
+ 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))
+
+ def SetConsoleTitle(title):
+ return _SetConsoleTitleW(title)
diff --git a/lib/Python/Lib/colorama/winterm.py b/lib/Python/Lib/colorama/winterm.py
new file mode 100644
index 000000000..fcc774ffa
--- /dev/null
+++ b/lib/Python/Lib/colorama/winterm.py
@@ -0,0 +1,151 @@
+# 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
+ BRIGHT_BACKGROUND = 0x80 # dim text, bright 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 | WinStyle.BRIGHT_BACKGROUND)
+
+ def reset_all(self, on_stderr=None):
+ self.set_attrs(self._default)
+ self.set_console(attrs=self._default)
+
+ def fore(self, fore=None, light=False, on_stderr=False):
+ if fore is None:
+ fore = self._default_fore
+ self._fore = fore
+ if light:
+ self._style |= WinStyle.BRIGHT
+ self.set_console(on_stderr=on_stderr)
+
+ def back(self, back=None, light=False, on_stderr=False):
+ if back is None:
+ back = self._default_back
+ self._back = back
+ if light:
+ self._style |= WinStyle.BRIGHT_BACKGROUND
+ 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_adjust(self, x, y, on_stderr=False):
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ position = self.get_position(handle)
+ adjusted_position = (position.Y + y, position.X + x)
+ win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
+
+ def erase_screen(self, mode=0, on_stderr=False):
+ # 0 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 move cursor to (1,1)
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ csbi = win32.GetConsoleScreenBufferInfo(handle)
+ # get the number of character cells in the current buffer
+ cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
+ # get number of character cells before current cursor position
+ cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
+ if mode == 0:
+ from_coord = csbi.dwCursorPosition
+ cells_to_erase = cells_in_screen - cells_before_cursor
+ if mode == 1:
+ from_coord = win32.COORD(0, 0)
+ cells_to_erase = cells_before_cursor
+ elif mode == 2:
+ from_coord = win32.COORD(0, 0)
+ cells_to_erase = cells_in_screen
+ # fill the entire screen with blanks
+ win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
+ # now set the buffer's attributes accordingly
+ win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
+ if mode == 2:
+ # put the cursor where needed
+ win32.SetConsoleCursorPosition(handle, (1, 1))
+
+ def erase_line(self, mode=0, on_stderr=False):
+ # 0 should clear from the cursor to the end of the line.
+ # 1 should clear from the cursor to the beginning of the line.
+ # 2 should clear the entire line.
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ csbi = win32.GetConsoleScreenBufferInfo(handle)
+ if mode == 0:
+ from_coord = csbi.dwCursorPosition
+ cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
+ if mode == 1:
+ from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
+ cells_to_erase = csbi.dwCursorPosition.X
+ elif mode == 2:
+ from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
+ cells_to_erase = csbi.dwSize.X
+ # fill the entire screen with blanks
+ win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
+ # now set the buffer's attributes accordingly
+ win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
+
+ def set_title(self, title):
+ win32.SetConsoleTitle(title)
diff --git a/lib/Python/Lib/colorlog/__init__.py b/lib/Python/Lib/colorlog/__init__.py
new file mode 100644
index 000000000..1d57f586d
--- /dev/null
+++ b/lib/Python/Lib/colorlog/__init__.py
@@ -0,0 +1,14 @@
+"""A logging formatter for colored output."""
+
+from __future__ import absolute_import
+
+from colorlog.colorlog import (
+ ColoredFormatter, escape_codes, default_log_colors)
+
+from colorlog.logging import (
+ basicConfig, root, getLogger, log,
+ debug, info, warning, error, exception, critical)
+
+__all__ = ('ColoredFormatter', 'default_log_colors', 'escape_codes',
+ 'basicConfig', 'root', 'getLogger', 'debug', 'info', 'warning',
+ 'error', 'exception', 'critical', 'log', 'exception')
diff --git a/lib/Python/Lib/colorlog/colorlog.py b/lib/Python/Lib/colorlog/colorlog.py
new file mode 100644
index 000000000..49860dd50
--- /dev/null
+++ b/lib/Python/Lib/colorlog/colorlog.py
@@ -0,0 +1,137 @@
+"""The ColoredFormatter class."""
+
+from __future__ import absolute_import
+
+import logging
+import collections
+import sys
+
+from colorlog.escape_codes import escape_codes, parse_colors
+
+__all__ = ('escape_codes', 'default_log_colors', 'ColoredFormatter')
+
+# The default colors to use for the debug levels
+default_log_colors = {
+ 'DEBUG': 'white',
+ 'INFO': 'green',
+ 'WARNING': 'yellow',
+ 'ERROR': 'red',
+ 'CRITICAL': 'bold_red',
+}
+
+# The default format to use for each style
+default_formats = {
+ '%': '%(log_color)s%(levelname)s:%(name)s:%(message)s',
+ '{': '{log_color}{levelname}:{name}:{message}',
+ '$': '${log_color}${levelname}:${name}:${message}'
+}
+
+
+class ColoredRecord(object):
+ """
+ Wraps a LogRecord and attempts to parse missing keys as escape codes.
+
+ When the record is formatted, the logging library uses ``record.__dict__``
+ directly - so this class replaced the dict with a ``defaultdict`` that
+ checks if a missing key is an escape code.
+ """
+
+ class __dict(collections.defaultdict):
+ def __missing__(self, name):
+ try:
+ return parse_colors(name)
+ except Exception:
+ raise KeyError("{} is not a valid record attribute "
+ "or color sequence".format(name))
+
+ def __init__(self, record):
+ # Replace the internal dict with one that can handle missing keys
+ self.__dict__ = self.__dict()
+ self.__dict__.update(record.__dict__)
+
+ # Keep a refrence to the original refrence so ``__getattr__`` can
+ # access functions that are not in ``__dict__``
+ self.__record = record
+
+ def __getattr__(self, name):
+ return getattr(self.__record, name)
+
+
+class ColoredFormatter(logging.Formatter):
+ """
+ A formatter that allows colors to be placed in the format string.
+
+ Intended to help in creating more readable logging output.
+ """
+
+ def __init__(self, fmt=None, datefmt=None,
+ log_colors=None, reset=True, style='%',
+ secondary_log_colors=None):
+ """
+ Set the format and colors the ColoredFormatter will use.
+
+ The ``fmt``, ``datefmt`` and ``style`` args are passed on to the
+ ``logging.Formatter`` constructor.
+
+ The ``secondary_log_colors`` argument can be used to create additional
+ ``log_color`` attributes. Each key in the dictionary will set
+ ``log_color_{key}``, using the value to select from a different
+ ``log_colors`` set.
+
+ :Parameters:
+ - fmt (str): The format string to use
+ - datefmt (str): A format string for the date
+ - log_colors (dict):
+ A mapping of log level names to color names
+ - reset (bool):
+ Implictly append a color reset to all records unless False
+ - style ('%' or '{' or '$'):
+ The format style to use. (*No meaning prior to Python 3.2.*)
+ - secondary_log_colors (dict):
+ Map secondary ``log_color`` attributes. (*New in version 2.6.*)
+ """
+ if fmt is None:
+ if sys.version_info > (3, 2):
+ fmt = default_formats[style]
+ else:
+ fmt = default_formats['%']
+
+ if sys.version_info > (3, 2):
+ super(ColoredFormatter, self).__init__(fmt, datefmt, style)
+ elif sys.version_info > (2, 7):
+ super(ColoredFormatter, self).__init__(fmt, datefmt)
+ else:
+ logging.Formatter.__init__(self, fmt, datefmt)
+
+ self.log_colors = (
+ log_colors if log_colors is not None else default_log_colors)
+ self.secondary_log_colors = secondary_log_colors
+ self.reset = reset
+
+ def color(self, log_colors, name):
+ """Return escape codes from a ``log_colors`` dict."""
+ return parse_colors(log_colors.get(name, ""))
+
+ def format(self, record):
+ """Format a message from a record object."""
+ record = ColoredRecord(record)
+ record.log_color = self.color(self.log_colors, record.levelname)
+
+ # Set secondary log colors
+ if self.secondary_log_colors:
+ for name, log_colors in self.secondary_log_colors.items():
+ color = self.color(log_colors, record.levelname)
+ setattr(record, name + '_log_color', color)
+
+ # Format the message
+ if sys.version_info > (2, 7):
+ message = super(ColoredFormatter, self).format(record)
+ else:
+ message = logging.Formatter.format(self, record)
+
+ # Add a reset code to the end of the message
+ # (if it wasn't explicitly added in format str)
+ if self.reset and not message.endswith(escape_codes['reset']):
+ message += escape_codes['reset']
+
+ return message
diff --git a/lib/Python/Lib/colorlog/escape_codes.py b/lib/Python/Lib/colorlog/escape_codes.py
new file mode 100644
index 000000000..848eb6489
--- /dev/null
+++ b/lib/Python/Lib/colorlog/escape_codes.py
@@ -0,0 +1,57 @@
+"""
+Generates a dictionary of ANSI escape codes.
+
+http://en.wikipedia.org/wiki/ANSI_escape_code
+
+Uses colorama as an optional dependancy to support color on Windows
+"""
+
+try:
+ import colorama
+except ImportError:
+ pass
+else:
+ colorama.init()
+
+__all__ = ('escape_codes', 'parse_colors')
+
+# Returns escape codes from format codes
+esc = lambda *x: '\033[' + ';'.join(x) + 'm'
+
+# The initial list of escape codes
+escape_codes = {
+ 'reset': esc('0'),
+ 'bold': esc('01'),
+}
+
+# The color names
+COLORS = [
+ 'black',
+ 'red',
+ 'green',
+ 'yellow',
+ 'blue',
+ 'purple',
+ 'cyan',
+ 'white'
+]
+
+PREFIXES = [
+ # Foreground without prefix
+ ('3', ''), ('01;3', 'bold_'),
+
+ # Foreground with fg_ prefix
+ ('3', 'fg_'), ('01;3', 'fg_bold_'),
+
+ # Background with bg_ prefix - bold/light works differently
+ ('4', 'bg_'), ('10', 'bg_bold_'),
+]
+
+for prefix, prefix_name in PREFIXES:
+ for code, name in enumerate(COLORS):
+ escape_codes[prefix_name + name] = esc(prefix + str(code))
+
+
+def parse_colors(sequence):
+ """Return escape codes from a color sequence."""
+ return ''.join(escape_codes[n] for n in sequence.split(',') if n)
diff --git a/lib/Python/Lib/colorlog/logging.py b/lib/Python/Lib/colorlog/logging.py
new file mode 100644
index 000000000..13f0c4ffb
--- /dev/null
+++ b/lib/Python/Lib/colorlog/logging.py
@@ -0,0 +1,44 @@
+"""Wrappers around the logging module."""
+
+from __future__ import absolute_import
+
+import functools
+import logging
+
+from colorlog.colorlog import ColoredFormatter
+
+BASIC_FORMAT = "%(log_color)s%(levelname)s%(reset)s:%(name)s:%(message)s"
+
+
+def basicConfig(**kwargs):
+ """Call ``logging.basicConfig`` and override the formatter it creates."""
+ logging.basicConfig(**kwargs)
+ logging._acquireLock()
+ try:
+ stream = logging.root.handlers[0]
+ stream.setFormatter(
+ ColoredFormatter(
+ fmt=kwargs.get('format', BASIC_FORMAT),
+ datefmt=kwargs.get('datefmt', None)))
+ finally:
+ logging._releaseLock()
+
+
+def ensure_configured(func):
+ """Modify a function to call ``basicConfig`` first if no handlers exist."""
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ if len(logging.root.handlers) == 0:
+ basicConfig()
+ return func(*args, **kwargs)
+ return wrapper
+
+root = logging.root
+getLogger = logging.getLogger
+debug = ensure_configured(logging.debug)
+info = ensure_configured(logging.info)
+warning = ensure_configured(logging.warning)
+error = ensure_configured(logging.error)
+critical = ensure_configured(logging.critical)
+log = ensure_configured(logging.log)
+exception = ensure_configured(logging.exception)
diff --git a/module/lib/jinja2/__init__.py b/lib/Python/Lib/jinja2/__init__.py
index f944e11b6..f944e11b6 100644
--- a/module/lib/jinja2/__init__.py
+++ b/lib/Python/Lib/jinja2/__init__.py
diff --git a/module/lib/jinja2/_markupsafe/__init__.py b/lib/Python/Lib/jinja2/_markupsafe/__init__.py
index ec7bd572d..ec7bd572d 100644
--- a/module/lib/jinja2/_markupsafe/__init__.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/__init__.py
diff --git a/module/lib/jinja2/_markupsafe/_bundle.py b/lib/Python/Lib/jinja2/_markupsafe/_bundle.py
index e694faf23..e694faf23 100644
--- a/module/lib/jinja2/_markupsafe/_bundle.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/_bundle.py
diff --git a/module/lib/jinja2/_markupsafe/_constants.py b/lib/Python/Lib/jinja2/_markupsafe/_constants.py
index 919bf03c5..919bf03c5 100644
--- a/module/lib/jinja2/_markupsafe/_constants.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/_constants.py
diff --git a/module/lib/jinja2/_markupsafe/_native.py b/lib/Python/Lib/jinja2/_markupsafe/_native.py
index 7b95828ec..7b95828ec 100644
--- a/module/lib/jinja2/_markupsafe/_native.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/_native.py
diff --git a/module/lib/jinja2/_markupsafe/tests.py b/lib/Python/Lib/jinja2/_markupsafe/tests.py
index c1ce3943a..c1ce3943a 100644
--- a/module/lib/jinja2/_markupsafe/tests.py
+++ b/lib/Python/Lib/jinja2/_markupsafe/tests.py
diff --git a/module/lib/jinja2/_stringdefs.py b/lib/Python/Lib/jinja2/_stringdefs.py
index 1161b7f4a..1161b7f4a 100644
--- a/module/lib/jinja2/_stringdefs.py
+++ b/lib/Python/Lib/jinja2/_stringdefs.py
diff --git a/module/lib/jinja2/bccache.py b/lib/Python/Lib/jinja2/bccache.py
index 1e2236c3a..1e2236c3a 100644
--- a/module/lib/jinja2/bccache.py
+++ b/lib/Python/Lib/jinja2/bccache.py
diff --git a/module/lib/jinja2/compiler.py b/lib/Python/Lib/jinja2/compiler.py
index 57641596a..57641596a 100644
--- a/module/lib/jinja2/compiler.py
+++ b/lib/Python/Lib/jinja2/compiler.py
diff --git a/module/lib/jinja2/constants.py b/lib/Python/Lib/jinja2/constants.py
index cab203cc7..cab203cc7 100644
--- a/module/lib/jinja2/constants.py
+++ b/lib/Python/Lib/jinja2/constants.py
diff --git a/module/lib/jinja2/debug.py b/lib/Python/Lib/jinja2/debug.py
index eb15456d1..eb15456d1 100644
--- a/module/lib/jinja2/debug.py
+++ b/lib/Python/Lib/jinja2/debug.py
diff --git a/module/lib/jinja2/defaults.py b/lib/Python/Lib/jinja2/defaults.py
index d2d45443a..d2d45443a 100644
--- a/module/lib/jinja2/defaults.py
+++ b/lib/Python/Lib/jinja2/defaults.py
diff --git a/module/lib/jinja2/environment.py b/lib/Python/Lib/jinja2/environment.py
index ac74a5c68..ac74a5c68 100644
--- a/module/lib/jinja2/environment.py
+++ b/lib/Python/Lib/jinja2/environment.py
diff --git a/module/lib/jinja2/exceptions.py b/lib/Python/Lib/jinja2/exceptions.py
index 771f6a8d7..771f6a8d7 100644
--- a/module/lib/jinja2/exceptions.py
+++ b/lib/Python/Lib/jinja2/exceptions.py
diff --git a/module/lib/jinja2/ext.py b/lib/Python/Lib/jinja2/ext.py
index ceb38953a..ceb38953a 100644
--- a/module/lib/jinja2/ext.py
+++ b/lib/Python/Lib/jinja2/ext.py
diff --git a/module/lib/jinja2/filters.py b/lib/Python/Lib/jinja2/filters.py
index d1848e434..d1848e434 100644
--- a/module/lib/jinja2/filters.py
+++ b/lib/Python/Lib/jinja2/filters.py
diff --git a/module/lib/jinja2/lexer.py b/lib/Python/Lib/jinja2/lexer.py
index 0d3f69617..0d3f69617 100644
--- a/module/lib/jinja2/lexer.py
+++ b/lib/Python/Lib/jinja2/lexer.py
diff --git a/module/lib/jinja2/loaders.py b/lib/Python/Lib/jinja2/loaders.py
index bd435e8b0..bd435e8b0 100644
--- a/module/lib/jinja2/loaders.py
+++ b/lib/Python/Lib/jinja2/loaders.py
diff --git a/module/lib/jinja2/meta.py b/lib/Python/Lib/jinja2/meta.py
index 3a779a5e9..3a779a5e9 100644
--- a/module/lib/jinja2/meta.py
+++ b/lib/Python/Lib/jinja2/meta.py
diff --git a/module/lib/jinja2/nodes.py b/lib/Python/Lib/jinja2/nodes.py
index 6446c70ea..6446c70ea 100644
--- a/module/lib/jinja2/nodes.py
+++ b/lib/Python/Lib/jinja2/nodes.py
diff --git a/module/lib/jinja2/optimizer.py b/lib/Python/Lib/jinja2/optimizer.py
index 00eab115e..00eab115e 100644
--- a/module/lib/jinja2/optimizer.py
+++ b/lib/Python/Lib/jinja2/optimizer.py
diff --git a/module/lib/jinja2/parser.py b/lib/Python/Lib/jinja2/parser.py
index d44229ad0..d44229ad0 100644
--- a/module/lib/jinja2/parser.py
+++ b/lib/Python/Lib/jinja2/parser.py
diff --git a/module/lib/jinja2/runtime.py b/lib/Python/Lib/jinja2/runtime.py
index 6fea3aa4f..6fea3aa4f 100644
--- a/module/lib/jinja2/runtime.py
+++ b/lib/Python/Lib/jinja2/runtime.py
diff --git a/module/lib/jinja2/sandbox.py b/lib/Python/Lib/jinja2/sandbox.py
index 749719548..749719548 100644
--- a/module/lib/jinja2/sandbox.py
+++ b/lib/Python/Lib/jinja2/sandbox.py
diff --git a/module/lib/jinja2/tests.py b/lib/Python/Lib/jinja2/tests.py
index d257eca0a..d257eca0a 100644
--- a/module/lib/jinja2/tests.py
+++ b/lib/Python/Lib/jinja2/tests.py
diff --git a/module/lib/jinja2/utils.py b/lib/Python/Lib/jinja2/utils.py
index 7b77b8eb7..7b77b8eb7 100644
--- a/module/lib/jinja2/utils.py
+++ b/lib/Python/Lib/jinja2/utils.py
diff --git a/module/lib/jinja2/visitor.py b/lib/Python/Lib/jinja2/visitor.py
index 413e7c309..413e7c309 100644
--- a/module/lib/jinja2/visitor.py
+++ b/lib/Python/Lib/jinja2/visitor.py
diff --git a/module/lib/rename_process.py b/lib/Python/Lib/rename_process.py
index 2527cef39..2527cef39 100644
--- a/module/lib/rename_process.py
+++ b/lib/Python/Lib/rename_process.py
diff --git a/lib/Python/Lib/send2trash/__init__.py b/lib/Python/Lib/send2trash/__init__.py
new file mode 100644
index 000000000..8a059a058
--- /dev/null
+++ b/lib/Python/Lib/send2trash/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+import sys
+
+if sys.platform == 'darwin':
+ from .plat_osx import send2trash
+elif sys.platform == 'win32':
+ from .plat_win import send2trash
+else:
+ try:
+ # If we can use gio, let's use it
+ from .plat_gio import send2trash
+ except ImportError:
+ # Oh well, let's fallback to our own Freedesktop trash implementation
+ from .plat_other import send2trash
diff --git a/lib/Python/Lib/send2trash/compat.py b/lib/Python/Lib/send2trash/compat.py
new file mode 100644
index 000000000..0f3a48972
--- /dev/null
+++ b/lib/Python/Lib/send2trash/compat.py
@@ -0,0 +1,13 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+import sys
+if sys.version < '3':
+ text_type = unicode
+ binary_type = str
+else:
+ text_type = str
+ binary_type = bytes
diff --git a/lib/Python/Lib/send2trash/plat_gio.py b/lib/Python/Lib/send2trash/plat_gio.py
new file mode 100644
index 000000000..9282d90a3
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_gio.py
@@ -0,0 +1,14 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+from gi.repository import GObject, Gio
+
+def send2trash(path):
+ try:
+ f = Gio.File.new_for_path(path)
+ f.trash(cancellable=None)
+ except GObject.GError as e:
+ raise OSError(e.message)
diff --git a/lib/Python/Lib/send2trash/plat_osx.py b/lib/Python/Lib/send2trash/plat_osx.py
new file mode 100644
index 000000000..fa2c2c85c
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_osx.py
@@ -0,0 +1,48 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+from __future__ import unicode_literals
+
+from ctypes import cdll, byref, Structure, c_char, c_char_p
+from ctypes.util import find_library
+
+from .compat import binary_type
+
+Foundation = cdll.LoadLibrary(find_library('Foundation'))
+CoreServices = cdll.LoadLibrary(find_library('CoreServices'))
+
+GetMacOSStatusCommentString = Foundation.GetMacOSStatusCommentString
+GetMacOSStatusCommentString.restype = c_char_p
+FSPathMakeRefWithOptions = CoreServices.FSPathMakeRefWithOptions
+FSMoveObjectToTrashSync = CoreServices.FSMoveObjectToTrashSync
+
+kFSPathMakeRefDefaultOptions = 0
+kFSPathMakeRefDoNotFollowLeafSymlink = 0x01
+
+kFSFileOperationDefaultOptions = 0
+kFSFileOperationOverwrite = 0x01
+kFSFileOperationSkipSourcePermissionErrors = 0x02
+kFSFileOperationDoNotMoveAcrossVolumes = 0x04
+kFSFileOperationSkipPreflight = 0x08
+
+class FSRef(Structure):
+ _fields_ = [('hidden', c_char * 80)]
+
+def check_op_result(op_result):
+ if op_result:
+ msg = GetMacOSStatusCommentString(op_result).decode('utf-8')
+ raise OSError(msg)
+
+def send2trash(path):
+ if not isinstance(path, binary_type):
+ path = path.encode('utf-8')
+ fp = FSRef()
+ opts = kFSPathMakeRefDoNotFollowLeafSymlink
+ op_result = FSPathMakeRefWithOptions(path, opts, byref(fp), None)
+ check_op_result(op_result)
+ opts = kFSFileOperationDefaultOptions
+ op_result = FSMoveObjectToTrashSync(byref(fp), None, opts)
+ check_op_result(op_result)
diff --git a/lib/Python/Lib/send2trash/plat_other.py b/lib/Python/Lib/send2trash/plat_other.py
new file mode 100644
index 000000000..59000eca9
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_other.py
@@ -0,0 +1,160 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+# This is a reimplementation of plat_other.py with reference to the
+# freedesktop.org trash specification:
+# [1] http://www.freedesktop.org/wiki/Specifications/trash-spec
+# [2] http://www.ramendik.ru/docs/trashspec.html
+# See also:
+# [3] http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+#
+# For external volumes this implementation will raise an exception if it can't
+# find or create the user's trash directory.
+
+from __future__ import unicode_literals
+
+import sys
+import os
+import os.path as op
+from datetime import datetime
+import stat
+try:
+ from urllib.parse import quote
+except ImportError:
+ # Python 2
+ from urllib import quote
+
+FILES_DIR = 'files'
+INFO_DIR = 'info'
+INFO_SUFFIX = '.trashinfo'
+
+# Default of ~/.local/share [3]
+XDG_DATA_HOME = op.expanduser(os.environ.get('XDG_DATA_HOME', '~/.local/share'))
+HOMETRASH = op.join(XDG_DATA_HOME, 'Trash')
+
+uid = os.getuid()
+TOPDIR_TRASH = '.Trash'
+TOPDIR_FALLBACK = '.Trash-' + str(uid)
+
+def is_parent(parent, path):
+ path = op.realpath(path) # In case it's a symlink
+ parent = op.realpath(parent)
+ return path.startswith(parent)
+
+def format_date(date):
+ return date.strftime("%Y-%m-%dT%H:%M:%S")
+
+def info_for(src, topdir):
+ # ...it MUST not include a ".."" directory, and for files not "under" that
+ # directory, absolute pathnames must be used. [2]
+ if topdir is None or not is_parent(topdir, src):
+ src = op.abspath(src)
+ else:
+ src = op.relpath(src, topdir)
+
+ info = "[Trash Info]\n"
+ info += "Path=" + quote(src) + "\n"
+ info += "DeletionDate=" + format_date(datetime.now()) + "\n"
+ return info
+
+def check_create(dir):
+ # use 0700 for paths [3]
+ if not op.exists(dir):
+ os.makedirs(dir, 0o700)
+
+def trash_move(src, dst, topdir=None):
+ filename = op.basename(src)
+ filespath = op.join(dst, FILES_DIR)
+ infopath = op.join(dst, INFO_DIR)
+ base_name, ext = op.splitext(filename)
+
+ counter = 0
+ destname = filename
+ while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)):
+ counter += 1
+ destname = '%s %s%s' % (base_name, counter, ext)
+
+ check_create(filespath)
+ check_create(infopath)
+
+ os.rename(src, op.join(filespath, destname))
+ f = open(op.join(infopath, destname + INFO_SUFFIX), 'w')
+ f.write(info_for(src, topdir))
+ f.close()
+
+def find_mount_point(path):
+ # Even if something's wrong, "/" is a mount point, so the loop will exit.
+ # Use realpath in case it's a symlink
+ path = op.realpath(path) # Required to avoid infinite loop
+ while not op.ismount(path):
+ path = op.split(path)[0]
+ return path
+
+def find_ext_volume_global_trash(volume_root):
+ # from [2] Trash directories (1) check for a .Trash dir with the right
+ # permissions set.
+ trash_dir = op.join(volume_root, TOPDIR_TRASH)
+ if not op.exists(trash_dir):
+ return None
+
+ mode = os.lstat(trash_dir).st_mode
+ # vol/.Trash must be a directory, cannot be a symlink, and must have the
+ # sticky bit set.
+ if not op.isdir(trash_dir) or op.islink(trash_dir) or not (mode & stat.S_ISVTX):
+ return None
+
+ trash_dir = op.join(trash_dir, str(uid))
+ try:
+ check_create(trash_dir)
+ except OSError:
+ return None
+ return trash_dir
+
+def find_ext_volume_fallback_trash(volume_root):
+ # from [2] Trash directories (1) create a .Trash-$uid dir.
+ trash_dir = op.join(volume_root, TOPDIR_FALLBACK)
+ # Try to make the directory, if we can't the OSError exception will escape
+ # be thrown out of send2trash.
+ check_create(trash_dir)
+ return trash_dir
+
+def find_ext_volume_trash(volume_root):
+ trash_dir = find_ext_volume_global_trash(volume_root)
+ if trash_dir is None:
+ trash_dir = find_ext_volume_fallback_trash(volume_root)
+ return trash_dir
+
+# Pull this out so it's easy to stub (to avoid stubbing lstat itself)
+def get_dev(path):
+ return os.lstat(path).st_dev
+
+def send2trash(path):
+ if not isinstance(path, str):
+ path = str(path, sys.getfilesystemencoding())
+ if not op.exists(path):
+ raise OSError("File not found: %s" % path)
+ # ...should check whether the user has the necessary permissions to delete
+ # it, before starting the trashing operation itself. [2]
+ if not os.access(path, os.W_OK):
+ raise OSError("Permission denied: %s" % path)
+ # if the file to be trashed is on the same device as HOMETRASH we
+ # want to move it there.
+ path_dev = get_dev(path)
+
+ # If XDG_DATA_HOME or HOMETRASH do not yet exist we need to stat the
+ # home directory, and these paths will be created further on if needed.
+ trash_dev = get_dev(op.expanduser('~'))
+
+ if path_dev == trash_dev:
+ topdir = XDG_DATA_HOME
+ dest_trash = HOMETRASH
+ else:
+ topdir = find_mount_point(path)
+ trash_dev = get_dev(topdir)
+ if trash_dev != path_dev:
+ raise OSError("Couldn't find mount point for %s" % path)
+ dest_trash = find_ext_volume_trash(topdir)
+ trash_move(path, dest_trash, topdir)
diff --git a/lib/Python/Lib/send2trash/plat_win.py b/lib/Python/Lib/send2trash/plat_win.py
new file mode 100644
index 000000000..3a55b9d3b
--- /dev/null
+++ b/lib/Python/Lib/send2trash/plat_win.py
@@ -0,0 +1,59 @@
+# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
+
+# This software is licensed under the "BSD" License as described in the "LICENSE" file,
+# which should be included with this package. The terms are also available at
+# http://www.hardcoded.net/licenses/bsd_license
+
+from __future__ import unicode_literals
+
+from ctypes import windll, Structure, byref, c_uint
+from ctypes.wintypes import HWND, UINT, LPCWSTR, BOOL
+import os.path as op
+
+from .compat import text_type
+
+shell32 = windll.shell32
+SHFileOperationW = shell32.SHFileOperationW
+
+class SHFILEOPSTRUCTW(Structure):
+ _fields_ = [
+ ("hwnd", HWND),
+ ("wFunc", UINT),
+ ("pFrom", LPCWSTR),
+ ("pTo", LPCWSTR),
+ ("fFlags", c_uint),
+ ("fAnyOperationsAborted", BOOL),
+ ("hNameMappings", c_uint),
+ ("lpszProgressTitle", LPCWSTR),
+ ]
+
+FO_MOVE = 1
+FO_COPY = 2
+FO_DELETE = 3
+FO_RENAME = 4
+
+FOF_MULTIDESTFILES = 1
+FOF_SILENT = 4
+FOF_NOCONFIRMATION = 16
+FOF_ALLOWUNDO = 64
+FOF_NOERRORUI = 1024
+
+def send2trash(path):
+ if not isinstance(path, text_type):
+ path = text_type(path, 'mbcs')
+ if not op.isabs(path):
+ path = op.abspath(path)
+ fileop = SHFILEOPSTRUCTW()
+ fileop.hwnd = 0
+ fileop.wFunc = FO_DELETE
+ fileop.pFrom = LPCWSTR(path + '\0')
+ fileop.pTo = None
+ fileop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT
+ fileop.fAnyOperationsAborted = 0
+ fileop.hNameMappings = 0
+ fileop.lpszProgressTitle = None
+ result = SHFileOperationW(byref(fileop))
+ if result:
+ msg = "Couldn't perform operation. Error code: %d" % result
+ raise OSError(msg)
+
diff --git a/module/lib/simplejson/__init__.py b/lib/Python/Lib/simplejson/__init__.py
index ef5c0db48..ef5c0db48 100644
--- a/module/lib/simplejson/__init__.py
+++ b/lib/Python/Lib/simplejson/__init__.py
diff --git a/module/lib/simplejson/decoder.py b/lib/Python/Lib/simplejson/decoder.py
index e5496d6e7..e5496d6e7 100644
--- a/module/lib/simplejson/decoder.py
+++ b/lib/Python/Lib/simplejson/decoder.py
diff --git a/module/lib/simplejson/encoder.py b/lib/Python/Lib/simplejson/encoder.py
index 5ec7440f1..5ec7440f1 100644
--- a/module/lib/simplejson/encoder.py
+++ b/lib/Python/Lib/simplejson/encoder.py
diff --git a/module/lib/simplejson/ordered_dict.py b/lib/Python/Lib/simplejson/ordered_dict.py
index 87ad88824..87ad88824 100644
--- a/module/lib/simplejson/ordered_dict.py
+++ b/lib/Python/Lib/simplejson/ordered_dict.py
diff --git a/module/lib/simplejson/scanner.py b/lib/Python/Lib/simplejson/scanner.py
index 54593a371..54593a371 100644
--- a/module/lib/simplejson/scanner.py
+++ b/lib/Python/Lib/simplejson/scanner.py
diff --git a/module/lib/simplejson/tool.py b/lib/Python/Lib/simplejson/tool.py
index 73370db55..73370db55 100644
--- a/module/lib/simplejson/tool.py
+++ b/lib/Python/Lib/simplejson/tool.py
diff --git a/module/lib/thrift/TSCons.py b/lib/Python/Lib/thrift/TSCons.py
index 24046256c..24046256c 100644
--- a/module/lib/thrift/TSCons.py
+++ b/lib/Python/Lib/thrift/TSCons.py
diff --git a/module/lib/thrift/TSerialization.py b/lib/Python/Lib/thrift/TSerialization.py
index b19f98aa8..b19f98aa8 100644
--- a/module/lib/thrift/TSerialization.py
+++ b/lib/Python/Lib/thrift/TSerialization.py
diff --git a/module/lib/thrift/Thrift.py b/lib/Python/Lib/thrift/Thrift.py
index 1d271fcff..1d271fcff 100644
--- a/module/lib/thrift/Thrift.py
+++ b/lib/Python/Lib/thrift/Thrift.py
diff --git a/module/lib/thrift/__init__.py b/lib/Python/Lib/thrift/__init__.py
index 48d659c40..48d659c40 100644
--- a/module/lib/thrift/__init__.py
+++ b/lib/Python/Lib/thrift/__init__.py
diff --git a/module/lib/thrift/protocol/TBase.py b/lib/Python/Lib/thrift/protocol/TBase.py
index e675c7dc0..e675c7dc0 100644
--- a/module/lib/thrift/protocol/TBase.py
+++ b/lib/Python/Lib/thrift/protocol/TBase.py
diff --git a/module/lib/thrift/protocol/TBinaryProtocol.py b/lib/Python/Lib/thrift/protocol/TBinaryProtocol.py
index 50c6aa896..50c6aa896 100644
--- a/module/lib/thrift/protocol/TBinaryProtocol.py
+++ b/lib/Python/Lib/thrift/protocol/TBinaryProtocol.py
diff --git a/module/lib/thrift/protocol/TCompactProtocol.py b/lib/Python/Lib/thrift/protocol/TCompactProtocol.py
index 016a33171..016a33171 100644
--- a/module/lib/thrift/protocol/TCompactProtocol.py
+++ b/lib/Python/Lib/thrift/protocol/TCompactProtocol.py
diff --git a/module/lib/thrift/protocol/TProtocol.py b/lib/Python/Lib/thrift/protocol/TProtocol.py
index 7338ff68a..7338ff68a 100644
--- a/module/lib/thrift/protocol/TProtocol.py
+++ b/lib/Python/Lib/thrift/protocol/TProtocol.py
diff --git a/module/lib/thrift/protocol/__init__.py b/lib/Python/Lib/thrift/protocol/__init__.py
index d53359b28..d53359b28 100644
--- a/module/lib/thrift/protocol/__init__.py
+++ b/lib/Python/Lib/thrift/protocol/__init__.py
diff --git a/module/lib/thrift/server/THttpServer.py b/lib/Python/Lib/thrift/server/THttpServer.py
index 3047d9c00..3047d9c00 100644
--- a/module/lib/thrift/server/THttpServer.py
+++ b/lib/Python/Lib/thrift/server/THttpServer.py
diff --git a/module/lib/thrift/server/TNonblockingServer.py b/lib/Python/Lib/thrift/server/TNonblockingServer.py
index ea348a0b6..ea348a0b6 100644
--- a/module/lib/thrift/server/TNonblockingServer.py
+++ b/lib/Python/Lib/thrift/server/TNonblockingServer.py
diff --git a/module/lib/thrift/server/TProcessPoolServer.py b/lib/Python/Lib/thrift/server/TProcessPoolServer.py
index 7ed814a88..7ed814a88 100644
--- a/module/lib/thrift/server/TProcessPoolServer.py
+++ b/lib/Python/Lib/thrift/server/TProcessPoolServer.py
diff --git a/module/lib/thrift/server/TServer.py b/lib/Python/Lib/thrift/server/TServer.py
index 8456e2d40..8456e2d40 100644
--- a/module/lib/thrift/server/TServer.py
+++ b/lib/Python/Lib/thrift/server/TServer.py
diff --git a/module/lib/thrift/server/__init__.py b/lib/Python/Lib/thrift/server/__init__.py
index 1bf6e254e..1bf6e254e 100644
--- a/module/lib/thrift/server/__init__.py
+++ b/lib/Python/Lib/thrift/server/__init__.py
diff --git a/module/lib/thrift/transport/THttpClient.py b/lib/Python/Lib/thrift/transport/THttpClient.py
index 50269785c..50269785c 100644
--- a/module/lib/thrift/transport/THttpClient.py
+++ b/lib/Python/Lib/thrift/transport/THttpClient.py
diff --git a/module/lib/thrift/transport/TSocket.py b/lib/Python/Lib/thrift/transport/TSocket.py
index 4e0e1874f..4e0e1874f 100644
--- a/module/lib/thrift/transport/TSocket.py
+++ b/lib/Python/Lib/thrift/transport/TSocket.py
diff --git a/module/lib/thrift/transport/TTransport.py b/lib/Python/Lib/thrift/transport/TTransport.py
index 12e51a9bf..12e51a9bf 100644
--- a/module/lib/thrift/transport/TTransport.py
+++ b/lib/Python/Lib/thrift/transport/TTransport.py
diff --git a/module/lib/thrift/transport/TTwisted.py b/lib/Python/Lib/thrift/transport/TTwisted.py
index b6dcb4e0b..b6dcb4e0b 100644
--- a/module/lib/thrift/transport/TTwisted.py
+++ b/lib/Python/Lib/thrift/transport/TTwisted.py
diff --git a/module/lib/thrift/transport/TZlibTransport.py b/lib/Python/Lib/thrift/transport/TZlibTransport.py
index 784d4e1e0..784d4e1e0 100644
--- a/module/lib/thrift/transport/TZlibTransport.py
+++ b/lib/Python/Lib/thrift/transport/TZlibTransport.py
diff --git a/module/lib/thrift/transport/__init__.py b/lib/Python/Lib/thrift/transport/__init__.py
index 46e54fe6b..46e54fe6b 100644
--- a/module/lib/thrift/transport/__init__.py
+++ b/lib/Python/Lib/thrift/transport/__init__.py
diff --git a/module/lib/wsgiserver/LICENSE.txt b/lib/Python/Lib/wsgiserver/LICENSE.txt
index a15165ee2..a15165ee2 100644
--- a/module/lib/wsgiserver/LICENSE.txt
+++ b/lib/Python/Lib/wsgiserver/LICENSE.txt
diff --git a/module/lib/wsgiserver/__init__.py b/lib/Python/Lib/wsgiserver/__init__.py
index c380e18b0..c380e18b0 100644
--- a/module/lib/wsgiserver/__init__.py
+++ b/lib/Python/Lib/wsgiserver/__init__.py
diff --git a/locale/cli.pot b/locale/cli.pot
deleted file mode 100644
index 646c6c70e..000000000
--- a/locale/cli.pot
+++ /dev/null
@@ -1,299 +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"
-
-#: pyLoadCli.py:75 pyLoadCli.py:133
-msgid " Command Line Interface"
-msgstr ""
-
-#: pyLoadCli.py:165
-#, python-format
-msgid "%s Downloads:"
-msgstr ""
-
-#: pyLoadCli.py:177
-msgid " Speed: "
-msgstr ""
-
-#: pyLoadCli.py:177
-msgid " Size: "
-msgstr ""
-
-#: pyLoadCli.py:178
-msgid " Finished in: "
-msgstr ""
-
-#: pyLoadCli.py:179
-msgid " ID: "
-msgstr ""
-
-#: pyLoadCli.py:184
-msgid "waiting: "
-msgstr ""
-
-#: pyLoadCli.py:191 pyLoadCli.py:193
-msgid "Status:"
-msgstr ""
-
-#: pyLoadCli.py:191
-msgid "paused"
-msgstr ""
-
-#: pyLoadCli.py:193
-msgid "running"
-msgstr ""
-
-#: pyLoadCli.py:196
-msgid "total Speed"
-msgstr ""
-
-#: pyLoadCli.py:196
-msgid "Files in queue"
-msgstr ""
-
-#: pyLoadCli.py:197
-msgid "Total"
-msgstr ""
-
-#: pyLoadCli.py:203
-msgid "Menu:"
-msgstr ""
-
-#: pyLoadCli.py:205
-msgid " Add Links"
-msgstr ""
-
-#: pyLoadCli.py:206
-msgid " Manage Queue"
-msgstr ""
-
-#: pyLoadCli.py:207
-msgid " Manage Collector"
-msgstr ""
-
-#: pyLoadCli.py:208
-msgid " (Un)Pause Server"
-msgstr ""
-
-#: pyLoadCli.py:209
-msgid " Kill Server"
-msgstr ""
-
-#: pyLoadCli.py:210
-msgid " Quit"
-msgstr ""
-
-#: pyLoadCli.py:289 pyLoadCli.py:296
-msgid "Please use this syntax: add <Package name> <link> <link2> ..."
-msgstr ""
-
-#: pyLoadCli.py:315
-#, python-format
-msgid "Checking %d links:"
-msgstr ""
-
-#: pyLoadCli.py:324
-msgid "File does not exists."
-msgstr ""
-
-#: pyLoadCli.py:385
-msgid "pyLoad was terminated"
-msgstr ""
-
-#: pyLoadCli.py:443
-msgid "Prints server status"
-msgstr ""
-
-#: pyLoadCli.py:444
-msgid "Prints downloads in queue"
-msgstr ""
-
-#: pyLoadCli.py:445
-msgid "Prints downloads in collector"
-msgstr ""
-
-#: pyLoadCli.py:446
-msgid "Adds package to queue"
-msgstr ""
-
-#: pyLoadCli.py:447
-msgid "Adds package to collector"
-msgstr ""
-
-#: pyLoadCli.py:448
-msgid "Delete Files from Queue/Collector"
-msgstr ""
-
-#: pyLoadCli.py:449
-msgid "Delete Packages from Queue/Collector"
-msgstr ""
-
-#: pyLoadCli.py:450
-msgid "Move Packages from Queue to Collector or vice versa"
-msgstr ""
-
-#: pyLoadCli.py:451
-msgid "Restart files"
-msgstr ""
-
-#: pyLoadCli.py:452
-msgid "Restart packages"
-msgstr ""
-
-#: pyLoadCli.py:453
-msgid "Check online status, works with local container"
-msgstr ""
-
-#: pyLoadCli.py:454
-msgid "Checks online status of a container file"
-msgstr ""
-
-#: pyLoadCli.py:455
-msgid "Pause the server"
-msgstr ""
-
-#: pyLoadCli.py:456
-msgid "continue downloads"
-msgstr ""
-
-#: pyLoadCli.py:457
-msgid "Toggle pause/unpause"
-msgstr ""
-
-#: pyLoadCli.py:458
-msgid "kill server"
-msgstr ""
-
-#: pyLoadCli.py:460
-msgid "List of commands:"
-msgstr ""
-
-#: pyLoadCli.py:473
-msgid "Couldn't write user config file"
-msgstr ""
-
-#: pyLoadCli.py:548
-msgid "You need py-openssl to connect to this pyLoad Core."
-msgstr ""
-
-#: pyLoadCli.py:555
-msgid "Address: "
-msgstr ""
-
-#: pyLoadCli.py:556
-msgid "Port: "
-msgstr ""
-
-#: pyLoadCli.py:557
-msgid "Username: "
-msgstr ""
-
-#: pyLoadCli.py:561
-msgid "Password: "
-msgstr ""
-
-#: pyLoadCli.py:566 pyLoadCli.py:575
-msgid "Login data is wrong."
-msgstr ""
-
-#: pyLoadCli.py:568 pyLoadCli.py:577
-#, python-format
-msgid "Could not establish connection to %(addr)s:%(port)s."
-msgstr ""
-
-#: pyLoadCli.py:580
-msgid "You need py-openssl to connect to this pyLoad core."
-msgstr ""
-
-#: pyLoadCli.py:582
-msgid "Interactive mode ignored since you passed some commands."
-msgstr ""
-
-#: module/cli/ManageFiles.py:97
-msgid "Manage Packages:"
-msgstr ""
-
-#: module/cli/ManageFiles.py:99
-msgid "Manage Links:"
-msgstr ""
-
-#: module/cli/ManageFiles.py:104
-msgid "What do you want to move?"
-msgstr ""
-
-#: module/cli/ManageFiles.py:106
-msgid "What do you want to delete?"
-msgstr ""
-
-#: module/cli/ManageFiles.py:108
-msgid "What do you want to restart?"
-msgstr ""
-
-#: module/cli/ManageFiles.py:113
-msgid "Choose what yout want to do or enter package number."
-msgstr ""
-
-#: module/cli/ManageFiles.py:115
-msgid "delete"
-msgstr ""
-
-#: module/cli/ManageFiles.py:115
-msgid "move"
-msgstr ""
-
-#: module/cli/ManageFiles.py:115
-msgid "restart"
-msgstr ""
-
-#: module/cli/ManageFiles.py:148
-msgid " - previous"
-msgstr ""
-
-#: module/cli/ManageFiles.py:148
-msgid " - next"
-msgstr ""
-
-#: module/cli/ManageFiles.py:149 module/cli/AddPackage.py:64
-msgid " back to main menu"
-msgstr ""
-
-#: module/cli/AddPackage.py:48
-msgid "Add Package:"
-msgstr ""
-
-#: module/cli/AddPackage.py:53
-msgid "Enter a name for the new package"
-msgstr ""
-
-#: module/cli/AddPackage.py:57
-#, python-format
-msgid "Package: %s"
-msgstr ""
-
-#: module/cli/AddPackage.py:58
-msgid "Parse the links you want to add."
-msgstr ""
-
-#: module/cli/AddPackage.py:59
-#, python-format
-msgid "Type %s when done."
-msgstr ""
-
-#: module/cli/AddPackage.py:60
-msgid "Links added: "
-msgstr ""
diff --git a/locale/core.pot b/locale/core.pot
deleted file mode 100644
index 546f0e4d3..000000000
--- a/locale/core.pot
+++ /dev/null
@@ -1,866 +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"
-
-#: pyLoadCore.py:185
-msgid "Received Quit signal"
-msgstr ""
-
-#: pyLoadCore.py:301
-#, python-format
-msgid "pyLoad already running with pid %s"
-msgstr ""
-
-#: pyLoadCore.py:315
-#, python-format
-msgid "Failed changing group: %s"
-msgstr ""
-
-#: pyLoadCore.py:325
-#, python-format
-msgid "Failed changing user: %s"
-msgstr ""
-
-#: pyLoadCore.py:327
-msgid "folder for logs"
-msgstr ""
-
-#: pyLoadCore.py:338
-msgid "Starting"
-msgstr ""
-
-#: pyLoadCore.py:339
-#, python-format
-msgid "Using home directory: %s"
-msgstr ""
-
-#: pyLoadCore.py:348
-msgid "pycrypto to decode container files"
-msgstr ""
-
-#: pyLoadCore.py:351
-msgid "folder for temporary files"
-msgstr ""
-
-#: pyLoadCore.py:356
-msgid "folder for downloads"
-msgstr ""
-
-#: pyLoadCore.py:359
-msgid "OpenSSL for secure connection"
-msgstr ""
-
-#: pyLoadCore.py:363
-msgid "Moving old user config to DB"
-msgstr ""
-
-#: pyLoadCore.py:366
-msgid "Please check your logindata with ./pyLoadCore.py -u"
-msgstr ""
-
-#: pyLoadCore.py:369
-msgid "All links removed"
-msgstr ""
-
-#: pyLoadCore.py:400
-#, python-format
-msgid "Downloadtime: %s"
-msgstr ""
-
-#: pyLoadCore.py:410
-#, python-format
-msgid "Free space: %s"
-msgstr ""
-
-#: pyLoadCore.py:430
-msgid "Activating Accounts..."
-msgstr ""
-
-#: pyLoadCore.py:436
-msgid "Activating Plugins..."
-msgstr ""
-
-#: pyLoadCore.py:439
-msgid "pyLoad is up and running"
-msgstr ""
-
-#: pyLoadCore.py:458
-msgid "restarting pyLoad"
-msgstr ""
-
-#: pyLoadCore.py:462
-msgid "pyLoad quits"
-msgstr ""
-
-#: pyLoadCore.py:519
-#, python-format
-msgid "Install %s"
-msgstr ""
-
-#: pyLoadCore.py:555
-#, python-format
-msgid "could not find %(desc)s: %(name)s"
-msgstr ""
-
-#: pyLoadCore.py:557
-#, python-format
-msgid "could not create %(desc)s: %(name)s"
-msgstr ""
-
-#: pyLoadCore.py:578
-msgid "shutting down..."
-msgstr ""
-
-#: pyLoadCore.py:595
-msgid "error while shutting down"
-msgstr ""
-
-#: pyLoadCore.py:659
-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"
-msgstr ""
-
-#: module/remote/ThriftBackend.py:39
-msgid "Using SSL ThriftBackend"
-msgstr ""
-
-#: module/remote/RemoteManager.py:35
-#, python-format
-msgid "Remote backend error: %s"
-msgstr ""
-
-#: module/remote/RemoteManager.py:82
-#, python-format
-msgid "Starting %(name)s: %(addr)s:%(port)s"
-msgstr ""
-
-#: module/remote/RemoteManager.py:84
-#, python-format
-msgid "Failed loading backend %(name)s | %(error)s"
-msgstr ""
-
-#: module/ThreadManager.py:137
-#, python-format
-msgid "Reconnect Failed: %s"
-msgstr ""
-
-#: module/ThreadManager.py:176
-msgid "Reconnect script not found!"
-msgstr ""
-
-#: module/ThreadManager.py:182
-msgid "Starting reconnect"
-msgstr ""
-
-#: module/ThreadManager.py:196
-msgid "Failed executing reconnect script!"
-msgstr ""
-
-#: module/ThreadManager.py:208
-#, python-format
-msgid "Reconnected, new IP: %s"
-msgstr ""
-
-#: module/ThreadManager.py:288
-msgid "Not enough space left on device"
-msgstr ""
-
-#: module/HookManager.py:90 module/plugins/Hook.py:102
-#, python-format
-msgid "Error executing hooks: %s"
-msgstr ""
-
-#: module/HookManager.py:140
-#, python-format
-msgid "Failed activating %(name)s"
-msgstr ""
-
-#: module/HookManager.py:144
-#, python-format
-msgid "Activated plugins: %s"
-msgstr ""
-
-#: module/HookManager.py:145
-#, python-format
-msgid "Deactivate plugins: %s"
-msgstr ""
-
-#: module/CaptchaManager.py:78 module/interaction/InteractionManager.py:82
-msgid "No Client connected for captcha decrypting"
-msgstr ""
-
-#: module/web/ServerThread.py:35
-msgid "SSL certificates not found."
-msgstr ""
-
-#: module/web/ServerThread.py:39
-#, python-format
-msgid "Sorry, we dropped support for starting %s directly within pyLoad"
-msgstr ""
-
-#: module/web/ServerThread.py:40
-msgid "You can use the threaded server which offers good performance and ssl,"
-msgstr ""
-
-#: module/web/ServerThread.py:41
-#, 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"
-msgstr ""
-
-#: module/web/ServerThread.py:49
-#, python-format
-msgid "Can't use %(server)s, python-flup is not installed!"
-msgstr ""
-
-#: module/web/ServerThread.py:56
-#, python-format
-msgid "Error importing lightweight server: %s"
-msgstr ""
-
-#: module/web/ServerThread.py:57
-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"
-msgstr ""
-
-#: module/web/ServerThread.py:59
-msgid ""
-"Of course you need to be familiar with linux and know how to compile software"
-msgstr ""
-
-#: module/web/ServerThread.py:63
-msgid "Server set to threaded, due to known performance problems on windows."
-msgstr ""
-
-#: module/web/ServerThread.py:80 module/web/ServerThread.py:103
-msgid "This server offers no SSL, please consider using threaded instead"
-msgstr ""
-
-#: module/web/ServerThread.py:82
-#, python-format
-msgid "Starting builtin webserver: %(host)s:%(port)d"
-msgstr ""
-
-#: module/web/ServerThread.py:87
-#, python-format
-msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
-msgstr ""
-
-#: module/web/ServerThread.py:91
-#, python-format
-msgid "Starting threaded webserver: %(host)s:%(port)d"
-msgstr ""
-
-#: module/web/ServerThread.py:97
-#, python-format
-msgid "Starting fastcgi server: %(host)s:%(port)d"
-msgstr ""
-
-#: module/web/ServerThread.py:105
-#, python-format
-msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
-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
-#, python-format
-msgid "waiting %s"
-msgstr ""
-
-#: module/Api.py:329
-#, python-format
-msgid "Added package %(name)s containing %(count)d links"
-msgstr ""
-
-#: module/Api.py:592
-#, 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"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:91
-#, python-format
-msgid "No %s installed"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:93
-#: module/plugins/hooks/ExtractArchive.py:98
-#, 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"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:117
-#, python-format
-msgid "Package %s queued for later extracting"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:142
-#, python-format
-msgid "Check package %s"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:179
-#, 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"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:237
-#, 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"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:251
-msgid "CRC Mismatch"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:255
-msgid "Unknown Error"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:307
-msgid "Setting User and Group failed"
-msgstr ""
-
-#: module/plugins/hooks/CaptchaTrader.py:71
-#, python-format
-msgid "%s credits left"
-msgstr ""
-
-#: module/plugins/hooks/CaptchaTrader.py:132
-msgid "Your CaptchaTrader Account has not enough credits"
-msgstr ""
-
-#: module/plugins/hooks/IRCInterface.py:74
-#: module/plugins/hooks/XMPPInterface.py:82
-#: module/database/FileDatabase.py:507
-#, python-format
-msgid "Package finished: %s"
-msgstr ""
-
-#: module/plugins/hooks/IRCInterface.py:81
-#, python-format
-msgid "Download finished: %(name)s @ %(plugin)s "
-msgstr ""
-
-#: module/plugins/hooks/IRCInterface.py:93
-#, python-format
-msgid "New Captcha Request: %s"
-msgstr ""
-
-#: module/plugins/hooks/IRCInterface.py:94
-#, python-format
-msgid "Answer with 'c %s text on the captcha'"
-msgstr ""
-
-#: module/plugins/hooks/XMPPInterface.py:90
-#, python-format
-msgid "Download finished: %(name)s @ %(plugin)s"
-msgstr ""
-
-#: module/plugins/hooks/RehostTo.py:32
-msgid "Please add your rehost.to account first and restart pyLoad"
-msgstr ""
-
-#: module/plugins/hooks/HotFolder.py:82
-#, python-format
-msgid "Added %s from HotFolder"
-msgstr ""
-
-#: module/plugins/hooks/ExternalScripts.py:54
-#, python-format
-msgid "Installed scripts for %s: "
-msgstr ""
-
-#: module/plugins/hooks/ExternalScripts.py:70
-msgid "Script not executable:"
-msgstr ""
-
-#: module/plugins/hooks/ExternalScripts.py:80
-#, python-format
-msgid "Error in %(script)s: %(error)s"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:68
-msgid "No Updates for pyLoad"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:73
-msgid "*** Plugins have been updated, please restart pyLoad ***"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:75
-msgid "Plugins updated and reloaded"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:78
-msgid "No plugin updates available"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:93
-#, python-format
-msgid "*** New pyLoad Version %s available ***"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:94
-msgid "*** Get it here: http://pyload.org/download ***"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:97
-#: module/plugins/hooks/UpdateManager.py:110
-msgid "Not able to connect server for updates"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:141
-#, python-format
-msgid "New version of %(type)s|%(name)s : %(version).2f"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:150
-#: module/plugins/hooks/UpdateManager.py:155
-#, python-format
-msgid "Error when updating %s"
-msgstr ""
-
-#: module/plugins/hooks/UpdateManager.py:155
-msgid "Version mismatch"
-msgstr ""
-
-#: module/plugins/hoster/BasePlugin.py:53
-msgid "Authorization required (username:password)"
-msgstr ""
-
-#: module/plugins/hoster/OronCom.py:135
-msgid "Not enough traffic left"
-msgstr ""
-
-#: module/plugins/hoster/OronCom.py:137
-#: module/plugins/hoster/UploadedTo.py:158
-msgid "Traffic exceeded"
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:135
-msgid "You should enable direct Download in your Megaupload Account settings"
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:158
-#, 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"
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:177
-msgid "The file is password protected, enter a password and restart."
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:194
-msgid "Megaupload is currently blocking your IP. Try again later, manually."
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:269
-msgid ""
-"Looks like the file is still not available. Retry downloading later, "
-"manually."
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:272
-msgid "Wrong password for download link."
-msgstr ""
-
-#: module/plugins/hoster/UploadedTo.py:131
-msgid "API key invalid"
-msgstr ""
-
-#: module/plugins/hoster/UploadedTo.py:155
-#, python-format
-msgid "%s: Not enough traffic left"
-msgstr ""
-
-#: module/plugins/hoster/ShareonlineBiz.py:106
-msgid "Parallel download issue"
-msgstr ""
-
-#: module/plugins/hoster/ShareonlineBiz.py:121
-msgid "Invalid download ticket"
-msgstr ""
-
-#: module/plugins/hoster/RehostTo.py:26
-msgid "Please enter your rehost.to account or deactivate this plugin"
-msgstr ""
-
-#: module/plugins/hoster/FileserveCom.py:87
-msgid "Not logged in."
-msgstr ""
-
-#: module/plugins/hoster/FileserveCom.py:112
-msgid "Parallel download error, now waiting 60s."
-msgstr ""
-
-#: module/plugins/hoster/RapidshareCom.py:99
-msgid "Rapidshare: Traffic Share (direct download)"
-msgstr ""
-
-#: module/plugins/hoster/RapidshareCom.py:126
-#: module/plugins/hoster/RapidshareCom.py:192
-msgid "Already downloading from this ip address, waiting 60 seconds"
-msgstr ""
-
-#: module/plugins/hoster/RapidshareCom.py:130
-msgid "Invalid Auth Code, download will be restarted"
-msgstr ""
-
-#: module/plugins/hoster/RapidshareCom.py:196
-msgid "RapidShareCom: No free slots"
-msgstr ""
-
-#: module/plugins/hoster/RapidshareCom.py:199
-msgid "You need a premium account for this file"
-msgstr ""
-
-#: module/plugins/hoster/RapidshareCom.py:201
-msgid "Filename reported invalid"
-msgstr ""
-
-#: module/plugins/hoster/FilesMailRu.py:99
-msgid "There was HTML Code in the Downloaded File("
-msgstr ""
-
-#: module/plugins/hoster/NetloadIn.py:141
-#: module/plugins/hoster/NetloadIn.py:161
-msgid "File temporarily not available"
-msgstr ""
-
-#: module/plugins/hoster/NetloadIn.py:174
-#, python-format
-msgid "Netload: waiting between downloads %d s."
-msgstr ""
-
-#: module/plugins/hoster/NetloadIn.py:203
-#, python-format
-msgid "Netload: waiting for captcha %d s."
-msgstr ""
-
-#: module/plugins/hoster/NetloadIn.py:242
-msgid "Downloaded File was empty"
-msgstr ""
-
-#: module/plugins/hoster/RealdebridCom.py:37
-msgid "Please enter your Real-debrid account or deactivate this plugin"
-msgstr ""
-
-#: module/plugins/container/LinkList.py:54
-msgid "LinkList could not be cleared."
-msgstr ""
-
-#: module/plugins/Plugin.py:381
-msgid ""
-"Pil and tesseract not installed and no Client connected for captcha "
-"decrypting"
-msgstr ""
-
-#: module/plugins/Plugin.py:385
-msgid "No captcha result obtained in appropiate time by any of the plugins."
-msgstr ""
-
-#: module/plugins/Plugin.py:490 module/plugins/Plugin.py:520
-#, python-format
-msgid "Setting User and Group failed: %s"
-msgstr ""
-
-#: module/plugins/Container.py:68
-msgid "File not exists."
-msgstr ""
-
-#: module/plugins/accounts/MegauploadCom.py:41
-msgid "Activate direct Download in your MegaUpload Account"
-msgstr ""
-
-#: module/plugins/accounts/FilesonicCom.py:49
-msgid "Invalid login retrieving user details"
-msgstr ""
-
-#: module/plugins/accounts/BitshareCom.py:36
-msgid "Activate direct Download in your Bitshare Account"
-msgstr ""
-
-#: module/plugins/PluginManager.py:153
-#, python-format
-msgid "%s has a invalid pattern."
-msgstr ""
-
-#: module/plugins/PluginManager.py:272
-#, python-format
-msgid "Error importing %(name)s: %(msg)s"
-msgstr ""
-
-#: module/plugins/AccountManager.py:88
-msgid "Account settings deleted, due to new config format."
-msgstr ""
-
-#: module/plugins/internal/MultiHoster.py:60
-msgid "No Hoster loaded"
-msgstr ""
-
-#: module/plugins/Account.py:85 module/plugins/Account.py:91
-#, python-format
-msgid "Could not login with account %(user)s | %(msg)s"
-msgstr ""
-
-#: module/plugins/Account.py:86
-msgid "Wrong Password"
-msgstr ""
-
-#: module/plugins/Account.py:240
-#, python-format
-msgid "Your Time %s has wrong format, use: 1:22-3:44"
-msgstr ""
-
-#: module/plugins/Account.py:266
-#, python-format
-msgid "Account %s has not enough traffic, checking again in 30min"
-msgstr ""
-
-#: module/plugins/Account.py:273
-#, python-format
-msgid "Account %s is expired, checking again in 1h"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "finished"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "offline"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "online"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "queued"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "skipped"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "waiting"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "temp. offline"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "starting"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "failed"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "aborted"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "decrypting"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "custom"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "downloading"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "processing"
-msgstr ""
-
-#: module/database/FileDatabase.py:47
-msgid "unknown"
-msgstr ""
-
-#: module/database/DatabaseBackend.py:174
-msgid "Filedatabase was deleted due to incompatible version."
-msgstr ""
-
-#: module/database/DatabaseBackend.py:189
-msgid "Filedatabase could NOT be converted."
-msgstr ""
-
-#: module/database/DatabaseBackend.py:198
-msgid "Database was converted from v2 to v3."
-msgstr ""
-
-#: module/database/DatabaseBackend.py:206
-msgid "Database was converted from v3 to v4."
-msgstr ""
-
-#: module/database/DatabaseBackend.py:252
-msgid "Converting old Django DB"
-msgstr ""
-
-#: module/network/HTTPDownload.py:245
-#, python-format
-msgid "Download chunks failed, fallback to single connection | %s"
-msgstr ""
-
-#: module/PluginThread.py:183
-#, python-format
-msgid "Download starts: %s"
-msgstr ""
-
-#: module/PluginThread.py:189
-#, python-format
-msgid "Download finished: %s"
-msgstr ""
-
-#: module/PluginThread.py:194 module/PluginThread.py:366
-#, python-format
-msgid "Plugin %s is missing a function."
-msgstr ""
-
-#: module/PluginThread.py:202 module/PluginThread.py:265
-#: module/PluginThread.py:383
-#, python-format
-msgid "Download aborted: %s"
-msgstr ""
-
-#: module/PluginThread.py:222
-#, python-format
-msgid "Download restarted: %(name)s | %(msg)s"
-msgstr ""
-
-#: module/PluginThread.py:231 module/PluginThread.py:374
-#, python-format
-msgid "Download is offline: %s"
-msgstr ""
-
-#: module/PluginThread.py:234
-#, python-format
-msgid "Download is temporary offline: %s"
-msgstr ""
-
-#: module/PluginThread.py:237 module/PluginThread.py:304
-#, python-format
-msgid "Download failed: %(name)s | %(msg)s"
-msgstr ""
-
-#: module/PluginThread.py:254
-msgid ""
-"Couldn't connect to host or connection reset, waiting 1 minute and retry."
-msgstr ""
-
-#: module/PluginThread.py:290
-#, python-format
-msgid "Download skipped: %(name)s due to %(plugin)s"
-msgstr ""
-
-#: module/PluginThread.py:362
-#, python-format
-msgid "Decrypting starts: %s"
-msgstr ""
-
-#: module/PluginThread.py:377 module/PluginThread.py:395
-#, python-format
-msgid "Decrypting failed: %(name)s | %(msg)s"
-msgstr ""
-
-#: module/PluginThread.py:389
-#, python-format
-msgid "Retrying %s"
-msgstr ""
-
-#: module/PluginThread.py:636
-#, python-format
-msgid "Info Fetching for %(name)s failed | %(err)s"
-msgstr ""
diff --git a/locale/crowdin.yaml b/locale/crowdin.yaml
new file mode 100644
index 000000000..ae932054f
--- /dev/null
+++ b/locale/crowdin.yaml
@@ -0,0 +1,12 @@
+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/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/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/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/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/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/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/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/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/django.pot b/locale/django.pot
deleted file mode 100644
index 81c9c7b6b..000000000
--- a/locale/django.pot
+++ /dev/null
@@ -1,686 +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"
-
-#: 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"
-msgstr ""
-
-#: module/web/translations.js:2 module/web/templates/default/captcha.html:7
-msgid "Please read the text on the captcha."
-msgstr ""
-
-#: module/web/translations.js:3
-msgid "Settings saved."
-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
-msgid "off"
-msgstr ""
-
-#: module/web/translations.js:5
-msgid "Success"
-msgstr ""
-
-#: module/web/translations.js:6
-msgid "Passwords did not match."
-msgstr ""
-
-#: module/web/translations.js:7
-msgid "Delete Link"
-msgstr ""
-
-#: module/web/translations.js:8
-msgid "pyLoad restarted"
-msgstr ""
-
-#: module/web/translations.js:9
-msgid "You are really sure you want to quit pyLoad?"
-msgstr ""
-
-#: module/web/translations.js:10
-msgid "Please Enter a packagename."
-msgstr ""
-
-#: module/web/translations.js:11
-msgid "Please click on the right captcha position."
-msgstr ""
-
-#: module/web/translations.js:12
-msgid "Error occured."
-msgstr ""
-
-#: module/web/translations.js:13
-msgid "New Captcha Request"
-msgstr ""
-
-#: module/web/translations.js:14
-msgid "Failed"
-msgstr ""
-
-#: module/web/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"
-msgstr ""
-
-#: module/web/translations.js:17
-msgid "Restart Link"
-msgstr ""
-
-#: module/web/translations.js:18
-msgid "New folder"
-msgstr ""
-
-#: module/web/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
-#, 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"
-msgstr ""
-
-#: module/web/templates/default/info.html:18
-msgid "News"
-msgstr ""
-
-#: module/web/templates/default/info.html:21
-msgid "Support"
-msgstr ""
-
-#: module/web/templates/default/info.html:37
-msgid "System"
-msgstr ""
-
-#: module/web/templates/default/info.html:40
-msgid "Python:"
-msgstr ""
-
-#: module/web/templates/default/info.html:44
-msgid "OS:"
-msgstr ""
-
-#: module/web/templates/default/info.html:48
-msgid "pyLoad version:"
-msgstr ""
-
-#: module/web/templates/default/info.html:52
-msgid "Installation Folder:"
-msgstr ""
-
-#: module/web/templates/default/info.html:56
-msgid "Config Folder:"
-msgstr ""
-
-#: module/web/templates/default/info.html:60
-msgid "Download Folder:"
-msgstr ""
-
-#: module/web/templates/default/info.html:64
-msgid "Free Space:"
-msgstr ""
-
-#: module/web/templates/default/info.html:68
-msgid "Language:"
-msgstr ""
-
-#: module/web/templates/default/info.html:72
-msgid "Webinterface Port:"
-msgstr ""
-
-#: module/web/templates/default/info.html:76
-msgid "Remote Interface Port:"
-msgstr ""
-
-#: module/web/templates/default/downloads.html:6
-#: module/web/templates/default/base.html:93
-#: module/web/templates/default/home.html:220
-msgid "Downloads"
-msgstr ""
-
-#: module/web/templates/default/filemanager.html:19
-msgid "FileManager"
-msgstr ""
-
-#: module/web/templates/default/admin.html:8
-#: module/web/templates/default/admin.html:9
-#: module/web/templates/default/base.html:59
-msgid "Administrate"
-msgstr ""
-
-#: module/web/templates/default/admin.html:13
-msgid "Quit pyLoad"
-msgstr ""
-
-#: module/web/templates/default/admin.html:14
-msgid "Restart pyLoad"
-msgstr ""
-
-#: module/web/templates/default/admin.html:18
-msgid "To add user or change passwords use:"
-msgstr ""
-
-#: module/web/templates/default/admin.html:19
-msgid "Important: Admin user have always all permissions!"
-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"
-msgstr ""
-
-#: module/web/templates/default/admin.html:28
-#: module/web/templates/default/admin.html:67
-msgid "Change Password"
-msgstr ""
-
-#: module/web/templates/default/admin.html:31
-msgid "Admin"
-msgstr ""
-
-#: module/web/templates/default/admin.html:34
-msgid "Permissions"
-msgstr ""
-
-#: module/web/templates/default/admin.html:41
-msgid "change"
-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
-msgid "Submit"
-msgstr ""
-
-#: module/web/templates/default/admin.html:69
-msgid "Enter your current and desired Password."
-msgstr ""
-
-#: module/web/templates/default/admin.html:70
-msgid "User"
-msgstr ""
-
-#: module/web/templates/default/admin.html:71
-#: module/web/templates/default/settings.html:179
-msgid "Your username."
-msgstr ""
-
-#: module/web/templates/default/admin.html:75
-msgid "Current password"
-msgstr ""
-
-#: module/web/templates/default/admin.html:76
-#: module/web/templates/default/settings.html:184
-msgid "The password for this account."
-msgstr ""
-
-#: module/web/templates/default/admin.html:80
-msgid "New password"
-msgstr ""
-
-#: module/web/templates/default/admin.html:81
-msgid "The new password."
-msgstr ""
-
-#: module/web/templates/default/admin.html:85
-msgid "New password (repeat)"
-msgstr ""
-
-#: module/web/templates/default/admin.html:86
-msgid "Please repeat the new password."
-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"
-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"
-msgstr ""
-
-#: module/web/templates/default/settings.html:16
-msgid "General"
-msgstr ""
-
-#: module/web/templates/default/settings.html:17
-msgid "Plugins"
-msgstr ""
-
-#: module/web/templates/default/settings.html:18
-msgid "Accounts"
-msgstr ""
-
-#: module/web/templates/default/settings.html:45
-#: module/web/templates/default/settings.html:74
-msgid "Choose a section from the menu"
-msgstr ""
-
-#: module/web/templates/default/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
-msgid "Premium"
-msgstr ""
-
-#: module/web/templates/default/settings.html:95
-msgid "Valid until"
-msgstr ""
-
-#: module/web/templates/default/settings.html:96
-msgid "Traffic left"
-msgstr ""
-
-#: module/web/templates/default/settings.html:97
-msgid "Time"
-msgstr ""
-
-#: module/web/templates/default/settings.html:98
-msgid "Max Parallel"
-msgstr ""
-
-#: module/web/templates/default/settings.html:99
-msgid "Delete?"
-msgstr ""
-
-#: module/web/templates/default/settings.html:121
-msgid "valid"
-msgstr ""
-
-#: module/web/templates/default/settings.html:124
-msgid "not valid"
-msgstr ""
-
-#: module/web/templates/default/settings.html:131
-msgid "yes"
-msgstr ""
-
-#: module/web/templates/default/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
-msgid "Add"
-msgstr ""
-
-#: module/web/templates/default/settings.html:176
-msgid "Add Account"
-msgstr ""
-
-#: module/web/templates/default/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"
-msgstr ""
-
-#: module/web/templates/default/settings.html:188
-msgid "Type"
-msgstr ""
-
-#: module/web/templates/default/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"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "absolute"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "relative"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:46
-msgid "name"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:47
-msgid "size"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:48
-msgid "type"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:49
-msgid "last modified"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:54
-msgid "parent directory"
-msgstr ""
-
-#: module/web/templates/default/pathchooser.html:70
-msgid "no content"
-msgstr ""
-
-#: module/web/templates/default/setup.html:3
-#: module/web/templates/default/setup.html:4
-msgid "Setup"
-msgstr ""
-
-#: module/web/templates/default/login.html:14
-msgid "Username"
-msgstr ""
-
-#: module/web/templates/default/login.html:29
-msgid "Your username and password didn't match. Please try again."
-msgstr ""
-
-#: module/web/templates/default/login.html:30
-msgid "To reset your login data or add an user run:"
-msgstr ""
-
-#: module/web/templates/default/base.html:20
-#: module/web/templates/default/base.html:139
-msgid "Webinterface"
-msgstr ""
-
-#: module/web/templates/default/base.html:39
-msgid "pyLoad Update available!"
-msgstr ""
-
-#: module/web/templates/default/base.html:46
-msgid "Plugins updated, please restart!"
-msgstr ""
-
-#: module/web/templates/default/base.html:52
-msgid "Captcha waiting"
-msgstr ""
-
-#: module/web/templates/default/base.html:57
-msgid "Logout"
-msgstr ""
-
-#: module/web/templates/default/base.html:61
-msgid "Info"
-msgstr ""
-
-#: module/web/templates/default/base.html:65
-msgid "Please Login!"
-msgstr ""
-
-#: module/web/templates/default/base.html:84
-#: module/web/templates/default/home.html:211
-msgid "Home"
-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"
-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"
-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"
-msgstr ""
-
-#: module/web/templates/default/base.html:114
-#: module/web/templates/default/logs.html:12
-msgid "Start"
-msgstr ""
-
-#: module/web/templates/default/base.html:115
-msgid "Stop"
-msgstr ""
-
-#: module/web/templates/default/base.html:116
-msgid "Cancel"
-msgstr ""
-
-#: module/web/templates/default/base.html:123
-msgid "Download:"
-msgstr ""
-
-#: module/web/templates/default/base.html:124
-msgid "Reconnect:"
-msgstr ""
-
-#: module/web/templates/default/base.html:125
-msgid "Speed:"
-msgstr ""
-
-#: module/web/templates/default/base.html:126
-msgid "Active:"
-msgstr ""
-
-#: module/web/templates/default/base.html:127
-msgid "Reload page"
-msgstr ""
-
-#: module/web/templates/default/base.html:157
-msgid "loading"
-msgstr ""
-
-#: module/web/templates/default/base.html:166
-msgid "Back to top"
-msgstr ""
-
-#: module/web/templates/default/logs.html:12
-msgid "prev"
-msgstr ""
-
-#: module/web/templates/default/logs.html:12
-msgid "next"
-msgstr ""
-
-#: module/web/templates/default/logs.html:12
-msgid "End"
-msgstr ""
-
-#: module/web/templates/default/logout.html:8
-msgid "You were successfully logged out."
-msgstr ""
-
-#: module/web/templates/default/queue.html:25
-msgid "Delete Finished"
-msgstr ""
-
-#: module/web/templates/default/queue.html:26
-msgid "Restart Failed"
-msgstr ""
-
-#: module/web/templates/default/queue.html:65
-msgid "Folder:"
-msgstr ""
-
-#: module/web/templates/default/queue.html:65
-msgid "Password:"
-msgstr ""
-
-#: module/web/templates/default/queue.html:79
-msgid "Edit Package"
-msgstr ""
-
-#: module/web/templates/default/queue.html:80
-msgid "Edit the package detais below."
-msgstr ""
-
-#: module/web/templates/default/queue.html:83
-msgid "The name of the package."
-msgstr ""
-
-#: module/web/templates/default/queue.html:87
-msgid "Folder"
-msgstr ""
-
-#: module/web/templates/default/queue.html:88
-msgid "Name of subfolder for these downloads."
-msgstr ""
-
-#: module/web/templates/default/queue.html:93
-msgid "List of passwords used for unrar."
-msgstr ""
-
-#: module/web/templates/default/window.html:5
-#: module/web/templates/default/window.html:40
-msgid "Add Package"
-msgstr ""
-
-#: module/web/templates/default/window.html:6
-msgid "Paste your links or upload a container."
-msgstr ""
-
-#: module/web/templates/default/window.html:8
-msgid "The name of the new package."
-msgstr ""
-
-#: module/web/templates/default/window.html:12
-msgid "Links"
-msgstr ""
-
-#: module/web/templates/default/window.html:13
-msgid "Paste your links here or any text and press the filter button."
-msgstr ""
-
-#: module/web/templates/default/window.html:14
-msgid "Filter urls"
-msgstr ""
-
-#: module/web/templates/default/window.html:22
-msgid "Password for RAR-Archive"
-msgstr ""
-
-#: module/web/templates/default/window.html:26
-msgid "File"
-msgstr ""
-
-#: module/web/templates/default/window.html:27
-msgid "Upload a container."
-msgstr ""
-
-#: module/web/templates/default/window.html:31
-msgid "Destination"
-msgstr ""
-
-#: module/web/templates/default/home.html:206
-msgid "Active Downloads"
-msgstr ""
-
-#: module/web/templates/default/home.html:240
-msgid "Size"
-msgstr ""
-
-#: module/web/templates/default/home.html:241
-msgid "Progress"
-msgstr ""
-
-#: module/web/templates/default/captcha.html:6
-msgid "Captcha reading"
-msgstr ""
-
-#: module/web/templates/default/captcha.html:13
-msgid "Captcha"
-msgstr ""
-
-#: module/web/templates/default/captcha.html:14
-msgid "The captcha."
-msgstr ""
-
-#: module/web/templates/default/captcha.html:20
-msgid "Text"
-msgstr ""
-
-#: module/web/templates/default/captcha.html:21
-msgid "Input the text on the captcha."
-msgstr ""
-
-#: module/web/templates/default/captcha.html:34
-msgid "Close"
-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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/pavement.py b/locale/pavement.py
new file mode 100644
index 000000000..06c1579e2
--- /dev/null
+++ b/locale/pavement.py
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from paver.easy import *
+from paver.setuputils import setup
+from paver.doctools import cog
+
+import glob
+import os
+import shutil
+import sys
+import re
+import subprocess
+import tempfile
+import urllib
+import zipfile
+
+
+PROJECT_DIR = path(__file__).dirname()
+sys.path.append(PROJECT_DIR)
+
+options = environment.options
+path("pyload").mkdir()
+
+extradeps = []
+if sys.version_info <= (2, 5):
+ extradeps += 'simplejson'
+
+setup(
+ name="pyload",
+ version="0.4.10",
+ description='Fast, lightweight and full featured download manager.',
+ 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_data=find_package_data(),
+ # data_files=[],
+ include_package_data=True,
+ 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,
+ extras_require={
+ 'SSL': ["pyOpenSSL"],
+ 'DLC': ['pycrypto'],
+ 'lightweight webserver': ['bjoern'],
+ },
+ # setup_requires=["setuptools_hg"],
+ entry_points={'console_scripts': ['pyLoadCore = pyLoadCore:main']},
+ zip_safe=False,
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Topic :: Internet :: WWW/HTTP",
+ "Environment :: Console",
+ "Environment :: Web Environment",
+ "Intended Audience :: End Users/Desktop",
+ "License :: OSI Approved :: GNU General Public License (GPL)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 2"
+ ]
+)
+
+options(
+ sphinx=Bunch(
+ builddir="_build",
+ sourcedir=""
+ ),
+ get_source=Bunch(
+ src="https://bitbucket.org/spoob/pyload/get/tip.zip",
+ rev=None,
+ clean=False
+ ),
+ thrift=Bunch(
+ path="../thrift/trunk/compiler/cpp/thrift",
+ gen=""
+ ),
+ virtualenv=Bunch(
+ dir="env",
+ python="python2",
+ virtual="virtualenv2",
+ ),
+ cog=Bunch(
+ pattern="*.py",
+ )
+)
+
+# xgettext args
+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") / "pyload"
+ pyload.rmtree()
+ subprocess.call_task('paver.doctools.html')
+
+
+@task
+@cmdopts([
+ ('src=', 's', 'Url to source'),
+ ('rev=', 'r', "HG revision"),
+ ("clean", 'c', 'Delete old source folder')
+])
+
+
+def get_source(options):
+ """ Downloads pyload source from bitbucket tip or given rev"""
+ if options.rev: options.url = "https://bitbucket.org/spoob/pyload/get/%s.zip" % options.rev
+
+ pyload = path("pyload")
+
+ if len(pyload.listdir()) and not options.clean:
+ return
+ elif pyload.exists():
+ pyload.rmtree()
+
+ urllib.urlretrieve(options.src, "pyload_src.zip")
+ zip = zipfile.ZipFile("pyload_src.zip")
+ zip.extractall()
+ path("pyload_src.zip").remove()
+
+ folder = [x for x in path(".").dirs() if x.name.startswith("spoob-pyload-")][0]
+ folder.shutil.move(pyload)
+
+ change_mode(pyload, 0644)
+ change_mode(pyload, 0755, folder=True)
+
+ for file in pyload.files():
+ if file.name.endswith(".py"):
+ file.chmod(0755)
+
+ (pyload / ".hgtags").remove()
+ (pyload / ".gitignore").remove()
+ #(pyload / "docs").rmtree()
+
+ f = open(pyload / "__init__.py", "wb")
+ f.close()
+
+ # options.setup.packages = find_packages()
+ # options.setup.package_data = find_package_data()
+
+
+@task
+@needs('clean', 'generate_setup', 'minilib', 'get_source', 'setuptools.command.sdist')
+def sdist():
+ """ Build source code package with distutils """
+
+
+@task
+@cmdopts([
+ ('path=', 'p', 'Thrift path'),
+ ('gen=', 'g', "Extra --gen option")
+])
+
+
+def thrift(options):
+ """ Generate Thrift stubs """
+
+ print "add import for TApplicationException manually as long it is not fixed"
+
+ outdir = path("pyload") / "remote" / "thriftbackend"
+ (outdir / "gen-py").rmtree()
+
+ cmd = [options.thrift.path, "-strict", "-o", outdir, "--gen", "py:slots, dynamic", outdir / "pyload.thrift"]
+
+ if options.gen:
+ cmd.insert(len(cmd) - 1, "--gen")
+ cmd.insert(len(cmd) - 1, options.gen)
+
+ print "running", cmd
+
+ p = subprocess.Popen(cmd)
+ p.communicate()
+
+ (outdir / "thriftgen").rmtree()
+ (outdir / "gen-py").shutil.move(outdir / "thriftgen")
+
+ # create light ttypes
+ from pyload.remote.socketbackend.create_ttypes import main
+ main()
+
+
+@task
+def compile_js():
+ """ Compile .coffee files to javascript"""
+
+ root = path("pyload") / "web" / "media" / "js"
+ for f in root.glob("*.coffee"):
+ print "generate", f
+ coffee = subprocess.Popen(["coffee", "-cbs"], stdin=open(f, "rb"), stdout=PIPE)
+ yui = subprocess.Popen(["yuicompressor", "--type", "js"], stdin=coffee.stdout, stdout=PIPE)
+ coffee.stdout.close()
+ content = yui.communicate()[0]
+ with open(root / f.name.replace(".coffee", ".js"), "wb") as js:
+ js.write("{% autoescape true %}\n")
+ js.write(content)
+ js.write("\n{% endautoescape %}")
+
+
+@task
+def generate_locale():
+ """ Generates localization files """
+
+ EXCLUDE = ["BeautifulSoup.py", "web/locale", "web/ajax", "web/cnl", "web/pyload",
+ "setup.py"]
+ makepot("core", path("pyload"), EXCLUDE, "./pyload.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("pyload/web").walkfiles():
+ if not fi.name.endswith(".js") and not fi.endswith(".coffee"):
+ continue
+ with open(fi, "rb") as c:
+ content = c.read()
+
+ strings.update(re.findall(r"_\s*\(\s*\"([^\"]+)", content))
+ strings.update(re.findall(r"_\s*\(\s*\'([^\']+)", content))
+
+ trans = path("pyload") / "web" / "translations.js"
+
+ with open(trans, "wb") as js:
+ for s in strings:
+ js.write('_("%s")\n' % s)
+
+ makepot("django", path("pyload/web"), EXCLUDE, "./%s\n" % trans.relpath(), [".py", ".html"], ["--language=Python"])
+
+ trans.remove()
+
+ path("includes.txt").remove()
+
+ print "Locale generated"
+
+
+@task
+@cmdopts([
+ ('key=', 'k', 'api key')
+])
+
+
+def upload_translations(options):
+ """ Uploads the locale files to translation server """
+ tmp = path(tempfile.mkdtemp())
+
+ shutil.shutil.copy('locale/crowdin.yaml', tmp)
+ os.mkdir(tmp / 'pyLoad')
+ for f in glob.glob('locale/*.pot'):
+ if os.path.isfile(f):
+ shutil.shutil.copy(f, tmp / 'pyLoad')
+
+ config = tmp / 'crowdin.yaml'
+ with open(config, 'rb') as f:
+ content = f.read()
+ content = content.format(key=options.key, tmp=tmp)
+ with open(config, 'wb') as f:
+ f.write(content)
+
+ subprocess.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(tempfile.mkdtemp())
+
+ shutil.shutil.copy('locale/crowdin.yaml', tmp)
+ os.mkdir(tmp / 'pyLoad')
+ for f in glob.glob('locale/*.pot'):
+ if os.path.isfile(f):
+ shutil.shutil.copy(f, tmp / 'pyLoad')
+
+ config = tmp / 'crowdin.yaml'
+ with open(config, 'rb') as f:
+ content = f.read()
+ content = content.format(key=options.key, tmp=tmp)
+ with open(config, 'wb') as f:
+ f.write(content)
+
+ subprocess.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.glob(language / 'LC_MESSAGES' / '*.po'):
+ print "Compiling %s" % f
+ subprocess.call(['msgfmt', '-o', f.replace('.po', '.mo'), f])
+
+
+@task
+def tests():
+ subprocess.call(["nosetests2"])
+
+
+@task
+def virtualenv(options):
+ """Setup virtual environment"""
+ if path(options.dir).exists():
+ return
+
+ subprocess.call([options.virtual, "--no-site-packages", "--python", options.python, options.dir])
+ print "$ source %s/bin/activate" % options.dir
+
+
+@task
+def clean_env():
+ """Deletes the virtual environment"""
+ env = path(options.virtualenv.dir)
+ if env.exists():
+ env.rmtree()
+
+
+@task
+@needs('generate_setup', 'minilib', 'get_source', 'virtualenv')
+def env_install():
+ """Install pyLoad into the virtualenv"""
+ venv = options.virtualenv
+ subprocess.call([path(venv.dir) / "bin" / "easy_install", "."])
+
+
+@task
+def clean():
+ """Cleans build directories"""
+ path("build").rmtree()
+ path("dist").rmtree()
+
+
+# helper functions
+
+
+def walk_trans(path, EXCLUDE, endings=[".py"]):
+ result = ""
+
+ for f in path.walkfiles():
+ if [True for x in EXCLUDE if x in f.dirname().relpath()]:
+ continue
+ if f.name in EXCLUDE:
+ continue
+
+ for e in endings:
+ if f.name.endswith(e):
+ result += "./%s\n" % f.relpath()
+ break
+
+ return result
+
+
+def makepot(domain, p, excludes=[], includes="", endings=[".py"], xxargs=[]):
+ print "Generate %s.pot" % domain
+
+ with open("includes.txt", "wb") as f:
+ if includes:
+ f.write(includes)
+
+ if p:
+ f.write(walk_trans(path(p), excludes, endings))
+
+ subprocess.call(["xgettext", "--files-from=includes.txt", "--default-domain=%s" % domain] + xargs + xxargs)
+
+ # replace charset und move file
+ with open("%s.po" % domain, "rb") as f:
+ content = f.read()
+
+ path("%s.po" % domain).remove()
+ content = content.replace("charset=CHARSET", "charset=UTF-8")
+
+ with open("locale/%s.pot" % domain, "wb") as f:
+ f.write(content)
+
+
+def change_owner(dir, uid, gid):
+ for p in dir.walk():
+ p.chown(uid, gid)
+
+
+def change_mode(dir, mode, folder=False):
+ for p in dir.walk():
+ if folder and p.isdir():
+ p.chmod(mode)
+ elif p.isfile() and not folder:
+ p.chmod(mode)
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/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/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/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/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/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/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/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/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/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/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/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/setup.pot b/locale/setup.pot
deleted file mode 100644
index cf4dd8cfc..000000000
--- a/locale/setup.pot
+++ /dev/null
@@ -1,495 +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"
-
-#: module/setup.py:51
-msgid "y"
-msgstr ""
-
-#: module/setup.py:53
-msgid "n"
-msgstr ""
-
-#: module/setup.py:72
-msgid "Welcome to the pyLoad Configuration Assistent."
-msgstr ""
-
-#: module/setup.py:73
-msgid ""
-"It will check your system and make a basic setup in order to run pyLoad."
-msgstr ""
-
-#: module/setup.py:75
-msgid "The value in brackets [] always is the default value,"
-msgstr ""
-
-#: module/setup.py:76
-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
-msgid ""
-"Don't forget: You can always rerun this assistent with --setup or -s "
-"parameter, when you start pyLoadCore."
-msgstr ""
-
-#: module/setup.py:78
-msgid "If you have any problems with this assistent hit STRG-C,"
-msgstr ""
-
-#: module/setup.py:79
-msgid "to abort and don't let him start with pyLoadCore automatically anymore."
-msgstr ""
-
-#: module/setup.py:81
-msgid "When you are ready for system check, hit enter."
-msgstr ""
-
-#: module/setup.py:88
-msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
-msgstr ""
-
-#: module/setup.py:89
-msgid "Please correct this and re-run pyLoad."
-msgstr ""
-
-#: module/setup.py:90
-msgid "Setup will now close."
-msgstr ""
-
-#: module/setup.py:94
-msgid "System check finished, hit enter to see your status report."
-msgstr ""
-
-#: module/setup.py:96
-msgid "## Status ##"
-msgstr ""
-
-#: module/setup.py:101
-msgid "container decrypting"
-msgstr ""
-
-#: module/setup.py:102
-msgid "ssl connection"
-msgstr ""
-
-#: module/setup.py:103
-msgid "automatic captcha decryption"
-msgstr ""
-
-#: module/setup.py:104
-msgid "GUI"
-msgstr ""
-
-#: module/setup.py:105
-msgid "Webinterface"
-msgstr ""
-
-#: module/setup.py:106
-msgid "extended Click'N'Load"
-msgstr ""
-
-#: module/setup.py:113
-msgid "Features available:"
-msgstr ""
-
-#: module/setup.py:117
-msgid "Featues missing: "
-msgstr ""
-
-#: module/setup.py:121
-msgid "no py-crypto available"
-msgstr ""
-
-#: module/setup.py:122
-msgid "You need this if you want to decrypt container files."
-msgstr ""
-
-#: module/setup.py:126
-msgid "no SSL available"
-msgstr ""
-
-#: module/setup.py:127
-msgid ""
-"This is needed if you want to establish a secure connection to core or "
-"webinterface."
-msgstr ""
-
-#: module/setup.py:128
-msgid "If you only want to access locally to pyLoad ssl is not usefull."
-msgstr ""
-
-#: module/setup.py:132
-msgid "no Captcha Recognition available"
-msgstr ""
-
-#: module/setup.py:133
-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
-msgid "no JavaScript engine found"
-msgstr ""
-
-#: module/setup.py:143
-msgid ""
-"You will need this for some Click'N'Load links. Install Spidermonkey, ossp-"
-"js, pyv8 or rhino"
-msgstr ""
-
-#: module/setup.py:145
-msgid "You can abort the setup now and fix some dependicies if you want."
-msgstr ""
-
-#: module/setup.py:147
-msgid "Continue with setup?"
-msgstr ""
-
-#: module/setup.py:153
-#, python-format
-msgid "Do you want to change the config path? Current is %s"
-msgstr ""
-
-#: module/setup.py:154
-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
-msgid "Change config path?"
-msgstr ""
-
-#: module/setup.py:162
-msgid "Do you want to configure login data and basic settings?"
-msgstr ""
-
-#: module/setup.py:163
-msgid "This is recommend for first run."
-msgstr ""
-
-#: module/setup.py:164
-msgid "Make basic setup?"
-msgstr ""
-
-#: module/setup.py:171
-msgid "Do you want to configure ssl?"
-msgstr ""
-
-#: module/setup.py:172
-msgid "Configure ssl?"
-msgstr ""
-
-#: module/setup.py:178
-msgid "Do you want to configure webinterface?"
-msgstr ""
-
-#: module/setup.py:179
-msgid "Configure webinterface?"
-msgstr ""
-
-#: module/setup.py:184
-msgid "Setup finished successfully."
-msgstr ""
-
-#: module/setup.py:185
-msgid "Hit enter to exit and restart pyLoad"
-msgstr ""
-
-#: module/setup.py:191
-msgid "## System Check ##"
-msgstr ""
-
-#: module/setup.py:196
-msgid "Your python version is to new, Please use Python 2.6/2.7"
-msgstr ""
-
-#: module/setup.py:199
-msgid "Your python version is to old, Please use at least Python 2.5"
-msgstr ""
-
-#: module/setup.py:202
-msgid "Python Version: OK"
-msgstr ""
-
-#: module/setup.py:249
-#, python-format
-msgid "Your installed jinja2 version %s seems too old."
-msgstr ""
-
-#: module/setup.py:250
-msgid "You can safely continue but if the webinterface is not working,"
-msgstr ""
-
-#: module/setup.py:251
-msgid ""
-"please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
-msgstr ""
-
-#: module/setup.py:268
-msgid "JS engine"
-msgstr ""
-
-#: module/setup.py:274
-msgid "## Basic Setup ##"
-msgstr ""
-
-#: module/setup.py:277
-msgid "The following logindata is valid for CLI, GUI and webinterface."
-msgstr ""
-
-#: module/setup.py:282 module/setup.py:371 module/setup.py:387
-msgid "Username"
-msgstr ""
-
-#: module/setup.py:288
-msgid ""
-"External clients (GUI, CLI or other) need remote access to work over the "
-"network."
-msgstr ""
-
-#: module/setup.py:289
-msgid ""
-"However, if you only want to use the webinterface you may disable it to save "
-"ram."
-msgstr ""
-
-#: module/setup.py:290
-msgid "Enable remote access"
-msgstr ""
-
-#: module/setup.py:295
-msgid "Language"
-msgstr ""
-
-#: module/setup.py:298
-msgid "Downloadfolder"
-msgstr ""
-
-#: module/setup.py:299
-msgid "Max parallel downloads"
-msgstr ""
-
-#: module/setup.py:303
-msgid "Use Reconnect?"
-msgstr ""
-
-#: module/setup.py:306
-msgid "Reconnect script location"
-msgstr ""
-
-#: module/setup.py:311
-msgid "## Webinterface Setup ##"
-msgstr ""
-
-#: module/setup.py:314
-msgid "Activate webinterface?"
-msgstr ""
-
-#: module/setup.py:316
-msgid ""
-"Listen address, if you use 127.0.0.1 or localhost, the webinterface will "
-"only accessible locally."
-msgstr ""
-
-#: module/setup.py:317
-msgid "Address"
-msgstr ""
-
-#: module/setup.py:318
-msgid "Port"
-msgstr ""
-
-#: module/setup.py:320
-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."
-msgstr ""
-
-#: module/setup.py:323
-msgid ""
-"Can be used by apache, lighttpd, requires you to configure them, which is "
-"not too easy job."
-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"
-msgstr ""
-
-#: module/setup.py:326
-msgid "and copy bjoern.so to module/lib"
-msgstr ""
-
-#: module/setup.py:329
-msgid ""
-"Attention: In some rare cases the builtin server is not working, if you "
-"notice problems with the webinterface"
-msgstr ""
-
-#: module/setup.py:330
-msgid "come back here and change the builtin server to the threaded one here."
-msgstr ""
-
-#: module/setup.py:333
-msgid "Server"
-msgstr ""
-
-#: module/setup.py:337
-msgid "## SSL Setup ##"
-msgstr ""
-
-#: module/setup.py:339
-msgid ""
-"Execute these commands from pyLoad config folder to make ssl certificates:"
-msgstr ""
-
-#: module/setup.py:345
-msgid "If you're done and everything went fine, you can activate ssl now."
-msgstr ""
-
-#: module/setup.py:347
-msgid "Activate SSL?"
-msgstr ""
-
-#: module/setup.py:361
-msgid "Select action"
-msgstr ""
-
-#: module/setup.py:362
-msgid "1 - Create/Edit user"
-msgstr ""
-
-#: module/setup.py:363
-msgid "2 - List users"
-msgstr ""
-
-#: module/setup.py:364
-msgid "3 - Remove user"
-msgstr ""
-
-#: module/setup.py:365
-msgid "4 - Quit"
-msgstr ""
-
-#: module/setup.py:377
-msgid "Users"
-msgstr ""
-
-#: module/setup.py:406
-msgid "Setting new configpath, current configuration will not be transfered!"
-msgstr ""
-
-#: module/setup.py:407
-msgid "Configpath"
-msgstr ""
-
-#: module/setup.py:415
-msgid "Configpath changed, setup will now close, please restart to go on."
-msgstr ""
-
-#: module/setup.py:416
-msgid "Press Enter to exit."
-msgstr ""
-
-#: module/setup.py:420
-#, python-format
-msgid "Setting config path failed: %s"
-msgstr ""
-
-#: module/setup.py:425
-#, python-format
-msgid "%s: OK"
-msgstr ""
-
-#: module/setup.py:427
-#, python-format
-msgid "%s: missing"
-msgstr ""
-
-#: module/setup.py:456
-msgid "[y]/n"
-msgstr ""
-
-#: module/setup.py:458
-msgid "y/[n]"
-msgstr ""
-
-#: module/setup.py:470
-msgid "Password: "
-msgstr ""
-
-#: module/setup.py:475
-msgid "Password to short. Use at least 4 symbols."
-msgstr ""
-
-#: module/setup.py:481
-msgid "Password (again): "
-msgstr ""
-
-#: module/setup.py:488
-msgid "Passwords did not match."
-msgstr ""
-
-#: module/setup.py:499
-msgid "yes"
-msgstr ""
-
-#: module/setup.py:499
-msgid "true"
-msgstr ""
-
-#: module/setup.py:499
-msgid "t"
-msgstr ""
-
-#: module/setup.py:502
-msgid "no"
-msgstr ""
-
-#: module/setup.py:502
-msgid "false"
-msgstr ""
-
-#: module/setup.py:502
-msgid "f"
-msgstr ""
-
-#: module/setup.py:505 module/setup.py:516
-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/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/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/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/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/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/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/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/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/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/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/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/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/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/network/__init__.py b/module/network/__init__.py
deleted file mode 100644
index 8b1378917..000000000
--- a/module/network/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/module/plugins/Account.py b/module/plugins/Account.py
deleted file mode 100644
index c147404e0..000000000
--- a/module/plugins/Account.py
+++ /dev/null
@@ -1,292 +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 random import choice
-from time import time
-from traceback import print_exc
-from threading import RLock
-
-from 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"
- __version__ = "0.2"
- __type__ = "account"
- __description__ = """Account Plugin"""
- __author_name__ = ("mkaay")
- __author_mail__ = ("mkaay@mkaay.de")
-
- #: after that time [in minutes] pyload will relogin the account
- login_timeout = 600
- #: account data will be reloaded after this time
- info_threshold = 600
-
-
- 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")})
- data["valid"] = False
-
- except Exception, e:
- self.logWarning(
- _("Could not login with account %(user)s | %(msg)s") % {"user": user
- , "msg": e})
- data["valid"] = False
- if self.core.debug:
- print_exc()
- finally:
- if req: req.close()
-
- 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
-
- 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.timestamps[user] + self.login_timeout * 60 < time():
- self.logDebug("Reached login timeout for %s" % user)
- self.relogin(user)
- return False
-
- return True
diff --git a/module/plugins/AccountManager.py b/module/plugins/AccountManager.py
deleted file mode 100644
index fc521d36c..000000000
--- a/module/plugins/AccountManager.py
+++ /dev/null
@@ -1,185 +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
-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 c233d3710..000000000
--- a/module/plugins/Container.py
+++ /dev/null
@@ -1,75 +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.plugins.Crypter import Crypter
-
-from os.path import join, exists, basename
-from os import remove
-import re
-
-class Container(Crypter):
- __name__ = "Container"
- __version__ = "0.1"
- __pattern__ = None
- __type__ = "container"
- __description__ = """Base container 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 = 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(join(pypath, self.pyfile.url)):
- self.pyfile.url = 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 d1549fe80..000000000
--- a/module/plugins/Crypter.py
+++ /dev/null
@@ -1,72 +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.plugins.Plugin import Plugin
-
-class Crypter(Plugin):
- __name__ = "Crypter"
- __version__ = "0.1"
- __pattern__ = None
- __type__ = "container"
- __description__ = """Base crypter 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 5efd08bae..000000000
--- a/module/plugins/Hook.py
+++ /dev/null
@@ -1,161 +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
- @interface-version: 0.2
-"""
-
-from traceback import print_exc
-
-from 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"
- __version__ = "0.2"
- __type__ = "hook"
- __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 \ No newline at end of file
diff --git a/module/plugins/Hoster.py b/module/plugins/Hoster.py
deleted file mode 100644
index 814a70949..000000000
--- a/module/plugins/Hoster.py
+++ /dev/null
@@ -1,33 +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.plugins.Plugin import Plugin
-
-def getInfo(self):
- #result = [ .. (name, size, status, url) .. ]
- return
-
-class Hoster(Plugin):
- __name__ = "Hoster"
- __version__ = "0.1"
- __pattern__ = None
- __type__ = "hoster"
- __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 15bf3971f..000000000
--- a/module/plugins/Plugin.py
+++ /dev/null
@@ -1,617 +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, spoob, mkaay
-"""
-
-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"
- __version__ = "0.4"
- __pattern__ = None
- __type__ = "hoster"
- __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 f3f5f47bc..000000000
--- a/module/plugins/PluginManager.py
+++ /dev/null
@@ -1,380 +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
-"""
-
-import re
-import sys
-
-from os import listdir, makedirs
-from os.path import isfile, join, exists, abspath
-from sys import version_info
-from itertools import chain
-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 range(0, 100)]
- print p.parseUrls(test)
-
- b = time()
-
- print b - a, "s"
-
diff --git a/module/plugins/__init__.py b/module/plugins/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/__init__.py
+++ /dev/null
diff --git a/module/plugins/accounts/AlldebridCom.py b/module/plugins/accounts/AlldebridCom.py
deleted file mode 100644
index f9c1f2ca6..000000000
--- a/module/plugins/accounts/AlldebridCom.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import xml.dom.minidom as dom
-
-from BeautifulSoup import BeautifulSoup
-
-from module.plugins.Account import Account
-
-
-class AlldebridCom(Account):
- __name__ = "AlldebridCom"
- __type__ = "account"
- __version__ = "0.23"
-
- __description__ = """AllDebrid.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt", "spamsales@online.de")]
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- html = req.load("http://www.alldebrid.com/account/")
- soup = BeautifulSoup(html)
-
- #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.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 Exception:
- data = self.getAccountData(user)
- html = req.load("http://www.alldebrid.com/api.php",
- get={'action': "info_user", 'login': user, 'pw': data['password']})
-
- self.logDebug(html)
-
- xml = dom.parseString(html)
- exp_time = 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):
- html = req.load("http://www.alldebrid.com/register/",
- get={'action' : "login",
- 'login_login' : user,
- 'login_password': data['password']},
- decode=True)
-
- if "This login doesn't exist" in html \
- or "The password is not valid" in html \
- or "Invalid captcha" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/BackinNet.py b/module/plugins/accounts/BackinNet.py
deleted file mode 100644
index 46c8d7ac5..000000000
--- a/module/plugins/accounts/BackinNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class BackinNet(XFSAccount):
- __name__ = "BackinNet"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Backin.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "backin.net"
diff --git a/module/plugins/accounts/BitshareCom.py b/module/plugins/accounts/BitshareCom.py
deleted file mode 100644
index 00e546f6d..000000000
--- a/module/plugins/accounts/BitshareCom.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class BitshareCom(Account):
- __name__ = "BitshareCom"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Bitshare account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Paul King", None)]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://bitshare.com/mysettings.html")
-
- if "\"http://bitshare.com/myupgrade.html\">Free" in html:
- return {"validuntil": -1, "trafficleft": -1, "premium": False}
-
- if not '<input type="checkbox" name="directdownload" checked="checked" />' in html:
- self.logWarning(_("Activate direct Download in your Bitshare Account"))
-
- return {"validuntil": -1, "trafficleft": -1, "premium": True}
-
-
- def login(self, user, data, req):
- html = req.load("http://bitshare.com/login.html",
- post={"user": user, "password": data['password'], "submit": "Login"},
- decode=True)
-
- if "login" in req.lastEffectiveURL:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CatShareNet.py b/module/plugins/accounts/CatShareNet.py
deleted file mode 100644
index bb42f443f..000000000
--- a/module/plugins/accounts/CatShareNet.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class CatShareNet(Account):
- __name__ = "CatShareNet"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """CatShareNet account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", None)]
-
-
- PREMIUM_PATTERN = r'<a href="/premium">Konto:[\s\n]*Premium'
- VALID_UNTIL_PATTERN = r'>Konto premium.*?<strong>(.*?)</strong></span>'
- TRAFFIC_LEFT_PATTERN = r'<a href="/premium">([0-9.]+ [kMG]B)'
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = -1
- trafficleft = -1
-
- html = req.load("http://catshare.net/", decode=True)
-
- if re.search(self.PREMIUM_PATTERN, html):
- premium = True
-
- try:
- expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
- self.logDebug("Expire date: " + expiredate)
-
- validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
-
- except Exception:
- pass
-
- try:
- trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
-
- except Exception:
- pass
-
- return {'premium': premium, 'trafficleft': trafficleft, 'validuntil': validuntil}
-
-
- def login(self, user, data, req):
- html = req.load("http://catshare.net/login",
- post={'user_email': user,
- 'user_password': data['password'],
- 'remindPassword': 0,
- 'user[submit]': "Login"},
- decode=True)
-
- if not '<a href="/logout">Wyloguj</a>' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CloudzillaTo.py b/module/plugins/accounts/CloudzillaTo.py
deleted file mode 100644
index d22d5e4b3..000000000
--- a/module/plugins/accounts/CloudzillaTo.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class CloudzillaTo(Account):
- __name__ = "CloudzillaTo"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Cloudzilla.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- PREMIUM_PATTERN = r'<h2>account type</h2>\s*Premium Account'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.cloudzilla.to/")
-
- premium = True if re.search(self.PREMIUM_PATTERN, html) else False
-
- return {'validuntil': -1, 'trafficleft': -1, 'premium': premium}
-
-
- def login(self, user, data, req):
- html = req.load("http://www.cloudzilla.to/",
- post={'lusername': user,
- 'lpassword': data['password'],
- 'w' : "dologin"},
- decode=True)
-
- if "ERROR" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CramitIn.py b/module/plugins/accounts/CramitIn.py
deleted file mode 100644
index a9e2274a2..000000000
--- a/module/plugins/accounts/CramitIn.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class CramitIn(XFSAccount):
- __name__ = "CramitIn"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Cramit.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "cramit.in"
diff --git a/module/plugins/accounts/CzshareCom.py b/module/plugins/accounts/CzshareCom.py
deleted file mode 100644
index 300943828..000000000
--- a/module/plugins/accounts/CzshareCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class CzshareCom(Account):
- __name__ = "CzshareCom"
- __type__ = "account"
- __version__ = "0.18"
-
- __description__ = """Czshare.com account plugin, now Sdilej.cz"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- CREDIT_LEFT_PATTERN = r'<tr class="active">\s*<td>([\d ,]+) (KiB|MiB|GiB)</td>\s*<td>([^<]*)</td>\s*</tr>'
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = None
- trafficleft = None
-
- html = req.load("http://sdilej.cz/prehled_kreditu/")
-
- try:
- m = re.search(self.CREDIT_LEFT_PATTERN, html)
- trafficleft = self.parseTraffic(m.group(1).replace(' ', '').replace(',', '.')) + m.group(2)
- validuntil = time.mktime(time.strptime(m.group(3), '%d.%m.%y %H:%M'))
-
- except Exception, e:
- self.logError(e)
-
- else:
- premium = True
-
- return {'premium' : premium,
- 'validuntil' : validuntil,
- 'trafficleft': trafficleft}
-
-
- def login(self, user, data, req):
- html = req.load('https://sdilej.cz/index.php',
- post={"Prihlasit": "Prihlasit",
- "login-password": data['password'],
- "login-name": user},
- decode=True)
-
- 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 3df99101a..000000000
--- a/module/plugins/accounts/DebridItaliaCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class DebridItaliaCom(Account):
- __name__ = "DebridItaliaCom"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Debriditalia.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- WALID_UNTIL_PATTERN = r'Premium valid till: (.+?) \|'
-
-
- def loadAccountInfo(self, user, req):
- info = {"premium": False, "validuntil": None, "trafficleft": None}
- html = req.load("http://debriditalia.com/")
-
- if 'Account premium not activated' not in html:
- m = re.search(self.WALID_UNTIL_PATTERN, html)
- if m:
- validuntil = time.mktime(time.strptime(m.group(1), "%d/%m/%Y %H:%M"))
- info = {"premium": True, "validuntil": validuntil, "trafficleft": -1}
- else:
- self.logError(_("Unable to retrieve account information"))
-
- return info
-
-
- def login(self, user, data, req):
- html = req.load("http://debriditalia.com/login.php",
- get={'u': user, 'p': data['password']},
- decode=True)
-
- if 'NO' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/DepositfilesCom.py b/module/plugins/accounts/DepositfilesCom.py
deleted file mode 100644
index a0bd6a37c..000000000
--- a/module/plugins/accounts/DepositfilesCom.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class DepositfilesCom(Account):
- __name__ = "DepositfilesCom"
- __type__ = "account"
- __version__ = "0.32"
-
- __description__ = """Depositfiles.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("https://dfiles.eu/de/gold/")
- validuntil = re.search(r"Sie haben Gold Zugang bis: <b>(.*?)</b></div>", html).group(1)
-
- validuntil = time.mktime(time.strptime(validuntil, "%Y-%m-%d %H:%M:%S"))
-
- return {"validuntil": validuntil, "trafficleft": -1}
-
-
- def login(self, user, data, req):
- html = req.load("https://dfiles.eu/de/login.php", get={"return": "/de/gold/payment.php"},
- post={"login": user, "password": data['password']},
- decode=True)
-
- if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/EasybytezCom.py b/module/plugins/accounts/EasybytezCom.py
deleted file mode 100644
index 93d3e2c19..000000000
--- a/module/plugins/accounts/EasybytezCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class EasybytezCom(XFSAccount):
- __name__ = "EasybytezCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """EasyBytez.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "easybytez.com"
diff --git a/module/plugins/accounts/EuroshareEu.py b/module/plugins/accounts/EuroshareEu.py
deleted file mode 100644
index db4539e2e..000000000
--- a/module/plugins/accounts/EuroshareEu.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class EuroshareEu(Account):
- __name__ = "EuroshareEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Euroshare.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 = time.mktime(time.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/ExashareCom.py b/module/plugins/accounts/ExashareCom.py
deleted file mode 100644
index 431798522..000000000
--- a/module/plugins/accounts/ExashareCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class ExashareCom(XFSAccount):
- __name__ = "ExashareCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Exashare.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "exashare.com"
diff --git a/module/plugins/accounts/FastixRu.py b/module/plugins/accounts/FastixRu.py
deleted file mode 100644
index 51be3880f..000000000
--- a/module/plugins/accounts/FastixRu.py
+++ /dev/null
@@ -1,41 +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.03"
-
- __description__ = """Fastix account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- html = json_loads(req.load("http://fastix.ru/api_v2/", get={'apikey': data['api'], 'sub': "getaccountdetails"}))
-
- points = html['points']
- kb = float(points) * 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):
- html = req.load("http://fastix.ru/api_v2/",
- get={'sub': "get_apikey", 'email': user, 'password': data['password']})
-
- api = json_loads(html)
- api = api['apikey']
-
- data['api'] = api
-
- if "error_code" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FastshareCz.py b/module/plugins/accounts/FastshareCz.py
deleted file mode 100644
index b946e29ba..000000000
--- a/module/plugins/accounts/FastshareCz.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class FastshareCz(Account):
- __name__ = "FastshareCz"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """Fastshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- CREDIT_PATTERN = r'Credit\s*:\s*</td>\s*<td>(.+?)\s*<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = -1
- trafficleft = None
- premium = False
-
- html = req.load("http://www.fastshare.cz/user", decode=True)
-
- m = re.search(self.CREDIT_PATTERN, html)
- if m:
- trafficleft = self.parseTraffic(m.group(1))
-
- premium = bool(trafficleft)
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("fastshare.cz", "lang", "en")
-
- 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={'login': user, 'heslo': data['password']},
- decode=True)
-
- if ">Wrong username or password" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/File4SafeCom.py b/module/plugins/accounts/File4SafeCom.py
deleted file mode 100644
index 50fe1aac8..000000000
--- a/module/plugins/accounts/File4SafeCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class File4SafeCom(XFSAccount):
- __name__ = "File4SafeCom"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """File4Safe.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- HOSTER_DOMAIN = "file4safe.com"
-
- LOGIN_FAIL_PATTERN = r'input_login'
diff --git a/module/plugins/accounts/FileParadoxIn.py b/module/plugins/accounts/FileParadoxIn.py
deleted file mode 100644
index c12d99d6a..000000000
--- a/module/plugins/accounts/FileParadoxIn.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class FileParadoxIn(XFSAccount):
- __name__ = "FileParadoxIn"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """FileParadox.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "fileparadox.in"
diff --git a/module/plugins/accounts/FilecloudIo.py b/module/plugins/accounts/FilecloudIo.py
deleted file mode 100644
index 8ca55b1bc..000000000
--- a/module/plugins/accounts/FilecloudIo.py
+++ /dev/null
@@ -1,59 +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.04"
-
- __description__ = """FilecloudIo account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "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 _i in xrange(5):
- rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api",
- post={"username": user, "password": self.getAccountData(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": float(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)
-
- if "you have successfully logged in" not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FilefactoryCom.py b/module/plugins/accounts/FilefactoryCom.py
deleted file mode 100644
index 37b6f97a8..000000000
--- a/module/plugins/accounts/FilefactoryCom.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FilefactoryCom(Account):
- __name__ = "FilefactoryCom"
- __type__ = "account"
- __version__ = "0.15"
-
- __description__ = """Filefactory.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "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 = time.mktime(time.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(pycurl.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 b92a371a5..000000000
--- a/module/plugins/accounts/FilejungleCom.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FilejungleCom(Account):
- __name__ = "FilejungleCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """Filejungle.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- login_timeout = 60
-
- URL = "http://filejungle.com/"
- TRAFFIC_LEFT_PATTERN = r'"/extend_premium\.php">Until (\d+ \w+ \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 = time.mktime(time.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": ""},
- decode=True)
-
- if re.search(self.LOGIN_FAILED_PATTERN, html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/FileomCom.py b/module/plugins/accounts/FileomCom.py
deleted file mode 100644
index 7c743f56a..000000000
--- a/module/plugins/accounts/FileomCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class FileomCom(XFSAccount):
- __name__ = "FileomCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Fileom.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "fileom.com"
diff --git a/module/plugins/accounts/FilerNet.py b/module/plugins/accounts/FilerNet.py
deleted file mode 100644
index ac5fd11da..000000000
--- a/module/plugins/accounts/FilerNet.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FilerNet(Account):
- __name__ = "FilerNet"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Filer.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "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 = time.mktime(time.strptime(until.group(1), "%d.%m.%Y %H:%M:%S"))
- trafficleft = self.parseTraffic(traffic.group(1))
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
-
- else:
- self.logError(_("Unable to retrieve account information"))
- 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/"},
- decode=True)
-
- 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 4c6755293..000000000
--- a/module/plugins/accounts/FilerioCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class FilerioCom(XFSAccount):
- __name__ = "FilerioCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """FileRio.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "filerio.in"
diff --git a/module/plugins/accounts/FilesMailRu.py b/module/plugins/accounts/FilesMailRu.py
deleted file mode 100644
index ee309c425..000000000
--- a/module/plugins/accounts/FilesMailRu.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class FilesMailRu(Account):
- __name__ = "FilesMailRu"
- __type__ = "account"
- __version__ = "0.11"
-
- __description__ = """Filesmail.ru account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def loadAccountInfo(self, user, req):
- return {"validuntil": None, "trafficleft": None}
-
-
- def login(self, user, data, req):
- user, domain = user.split("@")
-
- html = req.load("http://swa.mail.ru/cgi-bin/auth",
- post={"Domain": domain,
- "Login": user,
- "Password": data['password'],
- "Page": "http://files.mail.ru/"},
- decode=True)
-
- if "НеверМПе ОЌя пПльзПвателя ОлО парПль" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FileserveCom.py b/module/plugins/accounts/FileserveCom.py
deleted file mode 100644
index 5eb6b844c..000000000
--- a/module/plugins/accounts/FileserveCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FileserveCom(Account):
- __name__ = "FileserveCom"
- __type__ = "account"
- __version__ = "0.20"
-
- __description__ = """Fileserve.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
-
- html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
- "submit": "Submit+Query"})
- res = json_loads(html)
-
- if res['type'] == "premium":
- validuntil = time.mktime(time.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):
- html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
- "submit": "Submit+Query"})
- res = json_loads(html)
-
- if not res['type']:
- self.wrongPassword()
-
- #login at fileserv html
- 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 2777a142a..000000000
--- a/module/plugins/accounts/FourSharedCom.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class FourSharedCom(Account):
- __name__ = "FourSharedCom"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """FourShared.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "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")
-
- res = 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"},
- decode=True)
-
- if 'Please log in to access your 4shared account' in res:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FreakshareCom.py b/module/plugins/accounts/FreakshareCom.py
deleted file mode 100644
index ca3602a2c..000000000
--- a/module/plugins/accounts/FreakshareCom.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FreakshareCom(Account):
- __name__ = "FreakshareCom"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Freakshare.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = None
- trafficleft = None
-
- html = req.load("http://freakshare.com/")
-
- try:
- m = re.search(r'ltig bis:</td>\s*<td><b>([\d.:-]+)</b></td>', html, re.M)
- validuntil = time.mktime(time.strptime(m.group(1).strip(), "%d.%m.%Y - %H:%M"))
-
- except Exception:
- pass
-
- try:
- m = re.search(r'Traffic verbleibend:</td>\s*<td>([^<]+)', html, re.M)
- trafficleft = self.parseTraffic(m.group(1))
-
- except Exception:
- pass
-
- return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
-
-
- def login(self, user, data, req):
- req.load("http://freakshare.com/index.php?language=EN")
-
- html = req.load("http://freakshare.com/login.html",
- post={"submit": "Login", "user": user, "pass": data['password']},
- decode=True)
-
- if ">Wrong Username or Password" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FreeWayMe.py b/module/plugins/accounts/FreeWayMe.py
deleted file mode 100644
index 14b9f1e9a..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.13"
-
- __description__ = """FreeWayMe account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Nicolas Giese", "james@free-way.me")]
-
-
- def loadAccountInfo(self, user, req):
- status = self.getAccountStatus(user, req)
-
- self.logDebug(status)
-
- account_info = {"validuntil": -1, "premium": False}
- if status['premium'] == "Free":
- account_info['trafficleft'] = self.parseTraffic(status['guthaben'] + "MB")
- elif status['premium'] == "Spender":
- account_info['trafficleft'] = -1
- elif status['premium'] == "Flatrate":
- account_info = {"validuntil": float(status['Flatrate']),
- "trafficleft": -1,
- "premium": True}
-
- return account_info
-
-
- 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.getAccountData(user)['password']})
-
- self.logDebug("Login: %s" % answer)
-
- if answer == "Invalid login":
- self.wrongPassword()
-
- return json_loads(answer)
diff --git a/module/plugins/accounts/FshareVn.py b/module/plugins/accounts/FshareVn.py
deleted file mode 100644
index 7fcf88f20..000000000
--- a/module/plugins/accounts/FshareVn.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class FshareVn(Account):
- __name__ = "FshareVn"
- __type__ = "account"
- __version__ = "0.09"
-
- __description__ = """Fshare.vn account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "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.*?>([\d.]+) ([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 = time.mktime(time.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):
- html = req.load("https://www.fshare.vn/login.php",
- post={'LoginForm[email]' : user,
- 'LoginForm[password]' : data['password'],
- 'LoginForm[rememberMe]': 1,
- 'yt0' : "Login"},
- 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 self.parseTraffic(m.group(1) + 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 f978d2fa0..000000000
--- a/module/plugins/accounts/Ftp.py
+++ /dev/null
@@ -1,17 +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"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- info_threshold = 1000000
- login_timeout = 1000000
diff --git a/module/plugins/accounts/HellshareCz.py b/module/plugins/accounts/HellshareCz.py
deleted file mode 100644
index e559b28e1..000000000
--- a/module/plugins/accounts/HellshareCz.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class HellshareCz(Account):
- __name__ = "HellshareCz"
- __type__ = "account"
- __version__ = "0.16"
-
- __description__ = """Hellshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 = self.parseTraffic(credit + "MB")
- 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/', decode=True)
- 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, decode=True)
-
- 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"},
- decode=True)
-
- if "<p>You input a wrong user name or wrong password</p>" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/Http.py b/module/plugins/accounts/Http.py
deleted file mode 100644
index 07e46eb07..000000000
--- a/module/plugins/accounts/Http.py
+++ /dev/null
@@ -1,17 +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"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- info_threshold = 1000000
- login_timeout = 1000000
diff --git a/module/plugins/accounts/HugefilesNet.py b/module/plugins/accounts/HugefilesNet.py
deleted file mode 100644
index 5da3bbc37..000000000
--- a/module/plugins/accounts/HugefilesNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class HugefilesNet(XFSAccount):
- __name__ = "HugefilesNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Hugefiles.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "hugefiles.net"
diff --git a/module/plugins/accounts/HundredEightyUploadCom.py b/module/plugins/accounts/HundredEightyUploadCom.py
deleted file mode 100644
index 319a3feee..000000000
--- a/module/plugins/accounts/HundredEightyUploadCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class HundredEightyUploadCom(XFSAccount):
- __name__ = "HundredEightyUploadCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """180upload.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "180upload.com"
diff --git a/module/plugins/accounts/JunkyvideoCom.py b/module/plugins/accounts/JunkyvideoCom.py
deleted file mode 100644
index 8275ff176..000000000
--- a/module/plugins/accounts/JunkyvideoCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class JunkyvideoCom(XFSAccount):
- __name__ = "JunkyvideoCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Junkyvideo.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "junkyvideo.com"
diff --git a/module/plugins/accounts/JunocloudMe.py b/module/plugins/accounts/JunocloudMe.py
deleted file mode 100644
index b0fc160f3..000000000
--- a/module/plugins/accounts/JunocloudMe.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class JunocloudMe(XFSAccount):
- __name__ = "JunocloudMe"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Junocloud.me account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "junocloud.me"
diff --git a/module/plugins/accounts/Keep2ShareCc.py b/module/plugins/accounts/Keep2ShareCc.py
deleted file mode 100644
index d2ba1d237..000000000
--- a/module/plugins/accounts/Keep2ShareCc.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class Keep2ShareCc(Account):
- __name__ = "Keep2ShareCc"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Keep2Share.cc account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("aeronaut", "aeronaut@pianoguy.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'Premium expires:\s*<b>(.+?)<'
- TRAFFIC_LEFT_PATTERN = r'Available traffic \(today\):\s*<b><a href="/user/statistic.html">(.+?)<'
-
- LOGIN_FAIL_PATTERN = r'Please fix the following input errors'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = False
-
- html = req.load("http://keep2share.cc/site/profile.html", decode=True)
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- if expiredate == "LifeTime":
- premium = True
- validuntil = -1
- else:
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%Y.%m.%d"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- premium = True if validuntil > time.mktime(time.gmtime()) else False
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- try:
- trafficleft = self.parseTraffic(m.group(1))
-
- except Exception, e:
- self.logError(e)
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("keep2share.cc", "lang", "en")
-
- html = req.load("http://keep2share.cc/login.html",
- post={'LoginForm[username]' : user,
- 'LoginForm[password]' : data['password'],
- 'LoginForm[rememberMe]': 1,
- 'yt0' : ""},
- decode=True)
-
- if re.search(self.LOGIN_FAIL_PATTERN, html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/LetitbitNet.py b/module/plugins/accounts/LetitbitNet.py
deleted file mode 100644
index 7f973d2d3..000000000
--- a/module/plugins/accounts/LetitbitNet.py
+++ /dev/null
@@ -1,34 +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.02"
-
- __description__ = """Letitbit.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- ## DISABLED BECAUSE IT GET 'key exausted' EVEN IF VALID ##
- # api_key = self.getAccountData(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/LinestorageCom.py b/module/plugins/accounts/LinestorageCom.py
deleted file mode 100644
index a48d5beb9..000000000
--- a/module/plugins/accounts/LinestorageCom.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class LinestorageCom(XFSAccount):
- __name__ = "LinestorageCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Linestorage.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "linestorage.com"
- HOSTER_URL = "http://linestorage.com/"
diff --git a/module/plugins/accounts/LinksnappyCom.py b/module/plugins/accounts/LinksnappyCom.py
deleted file mode 100644
index 34571d374..000000000
--- a/module/plugins/accounts/LinksnappyCom.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import hashlib
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class LinksnappyCom(Account):
- __name__ = "LinksnappyCom"
- __type__ = "account"
- __version__ = "0.05"
- __description__ = """Linksnappy.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "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': hashlib.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 = self.parseTraffic("%d MB" % j['return']['trafficleft'])
-
- 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': hashlib.md5(data['password']).hexdigest()},
- decode=True)
-
- 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 a082b97af..000000000
--- a/module/plugins/accounts/MegaDebridEu.py
+++ /dev/null
@@ -1,39 +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.20"
-
- __description__ = """mega-debrid.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("D.Ducatel", "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']})
- res = json_loads(jsonResponse)
-
- if res['response_code'] == "ok":
- return {"premium": True, "validuntil": float(res['vip_end']), "status": True}
- else:
- self.logError(res)
- 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']})
- res = json_loads(jsonResponse)
- if res['response_code'] != "ok":
- self.wrongPassword()
diff --git a/module/plugins/accounts/MegaRapidCz.py b/module/plugins/accounts/MegaRapidCz.py
deleted file mode 100644
index 262d5a818..000000000
--- a/module/plugins/accounts/MegaRapidCz.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class MegaRapidCz(Account):
- __name__ = "MegaRapidCz"
- __type__ = "account"
- __version__ = "0.35"
-
- __description__ = """MegaRapid.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("MikyWoW", "mikywow@seznam.cz"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- login_timeout = 60
-
- LIMITDL_PATTERN = ur'<td>Max. počet paralelních stahování: </td><td>(\d+)'
- VALID_UNTIL_PATTERN = ur'<td>Paušální stahování aktivní. Vyprší </td><td><strong>(.*?)</strong>'
- TRAFFIC_LEFT_PATTERN = r'<tr><td>Kredit</td><td>(.*?) GiB'
-
-
- def loadAccountInfo(self, user, req):
- htmll = req.load("http://megarapid.cz/mujucet/", decode=True)
-
- m = re.search(self.LIMITDL_PATTERN, htmll)
- if m:
- data = self.getAccountData(user)
- data['options']['limitDL'] = [int(m.group(1))]
-
- m = re.search(self.VALID_UNTIL_PATTERN, htmll)
- if m:
- validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y - %H:%M"))
- return {"premium": True, "trafficleft": -1, "validuntil": validuntil}
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, htmll)
- 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):
- html = req.load("http://megarapid.cz/prihlaseni/", decode=True)
-
- if "Heslo:" in html:
- start = html.index('id="inp_hash" name="hash" value="')
- html = html[start + 33:]
- hashes = html[0:32]
- html = req.load("http://megarapid.cz/prihlaseni/",
- post={"hash": hashes,
- "login": user,
- "pass1": data['password'],
- "remember": 0,
- "sbmt": u"Přihlásit"})
diff --git a/module/plugins/accounts/MegaRapidoNet.py b/module/plugins/accounts/MegaRapidoNet.py
deleted file mode 100644
index d061d02bc..000000000
--- a/module/plugins/accounts/MegaRapidoNet.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class MegaRapidoNet(Account):
- __name__ = "MegaRapidoNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """MegaRapido.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- VALID_UNTIL_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?[\'"]premium_index[\'"].*?>[^<]*?<[^>]*?b.*?>\s*?TEMPO\s*?PREMIUM.*?<[^>]*?/b.*?>\s*?(\d*)[^\d]*?DIAS[^\d]*?(\d*)[^\d]*?HORAS[^\d]*?(\d*)[^\d]*?MINUTOS[^\d]*?(\d*)[^\d]*?SEGUNDOS'
- USER_ID_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']checkbox_compartilhar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']usar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']user["\'][^>]*?value\s*?=\s*?["\'](.*?)\s*?["\']'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = False
-
- html = req.load("http://megarapido.net/gerador", decode=True)
-
- validuntil = re.search(self.VALID_UNTIL_PATTERN, html)
- if validuntil:
- #hier weitermachen!!! (mÌssen umbedingt die zeit richtig machen damit! (sollte aber möglich))
- validuntil = time.time() + int(validuntil.group(1)) * 24 * 3600 + int(validuntil.group(2)) * 3600 + int(validuntil.group(3)) * 60 + int(validuntil.group(4))
- trafficleft = -1
- premium = True
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- req.load("http://megarapido.net/login")
- req.load("http://megarapido.net/painel_user/ajax/logar.php",
- post={'login': user, 'senha': data['password']},
- decode=True)
-
- html = req.load("http://megarapido.net/gerador")
-
- if "sair" not in html.lower():
- self.wrongPassword()
- else:
- m = re.search(self.USER_ID_PATTERN, html)
- if m:
- data['uid'] = m.group(1)
- else:
- self.fail("Couldn't find the user ID")
diff --git a/module/plugins/accounts/MegasharesCom.py b/module/plugins/accounts/MegasharesCom.py
deleted file mode 100644
index 3d7ddbe46..000000000
--- a/module/plugins/accounts/MegasharesCom.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class MegasharesCom(Account):
- __name__ = "MegasharesCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Megashares.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 = time.mktime(time.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 6128cddc8..000000000
--- a/module/plugins/accounts/MovReelCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class MovReelCom(XFSAccount):
- __name__ = "MovReelCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Movreel.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
- login_timeout = 60
- info_threshold = 30
-
- HOSTER_DOMAIN = "movreel.com"
diff --git a/module/plugins/accounts/MultihostersCom.py b/module/plugins/accounts/MultihostersCom.py
deleted file mode 100644
index e98f17b2d..000000000
--- a/module/plugins/accounts/MultihostersCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.accounts.ZeveraCom import ZeveraCom
-
-
-class MultihostersCom(ZeveraCom):
- __name__ = "MultihostersCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Multihosters.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("tjeh", "tjeh@gmx.net")]
-
-
- HOSTER_DOMAIN = "multihosters.com"
diff --git a/module/plugins/accounts/MultishareCz.py b/module/plugins/accounts/MultishareCz.py
deleted file mode 100644
index 3488e3288..000000000
--- a/module/plugins/accounts/MultishareCz.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class MultishareCz(Account):
- __name__ = "MultishareCz"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Multishare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- TRAFFIC_LEFT_PATTERN = r'<span class="profil-zvyrazneni">Kredit:</span>\s*<strong>(?P<S>[\d.,]+)&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 = self.parseTraffic(m.group('S') + m.group('U')) 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 9a13e2e42..000000000
--- a/module/plugins/accounts/MyfastfileCom.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class MyfastfileCom(Account):
- __name__ = "MyfastfileCom"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Myfastfile.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- if 'days_left' in self.json_data:
- validuntil = time.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 100644
index 066174a28..000000000
--- a/module/plugins/accounts/NetloadIn.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class NetloadIn(Account):
- __name__ = "NetloadIn"
- __type__ = "account"
- __version__ = "0.24"
-
- __description__ = """Netload.in account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def api_response(self, id, password, req):
- return req.load("http://api.netload.in/user_info.php",
- get={'auth' : "BVm96BWDSoB4WkfbEhn42HgnjIe1ilMt",
- 'user_id' : id,
- 'user_password': password}).strip()
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = False
-
- html = self.api_response(user, self.getAccountData(user)['password'], req)
-
- if html == "-1":
- premium = True
-
- elif html == "0":
- validuntil = -1
-
- else:
- try:
- validuntil = time.mktime(time.strptime(html, "%Y-%m-%d %H:%M"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- self.logDebug("Valid until: %s" % validuntil)
-
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- else:
- validuntil = -1
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- html = self.api_response(user, data['password'], req)
-
- if not html or re.search(r'disallowed_agent|unknown_auth|login_failed', html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/NoPremiumPl.py b/module/plugins/accounts/NoPremiumPl.py
deleted file mode 100644
index 7e3f757d3..000000000
--- a/module/plugins/accounts/NoPremiumPl.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import datetime
-import hashlib
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads as loads
-
-
-class NoPremiumPl(Account):
- __name__ = "NoPremiumPl"
- __version__ = "0.01"
- __type__ = "account"
- __description__ = "NoPremium.pl account plugin"
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@nopremium.pl")]
-
- _api_url = "http://crypt.nopremium.pl"
-
- _api_query = {
- "site": "nopremium",
- "username": "",
- "password": "",
- "output": "json",
- "loc": "1",
- "info": "1"
- }
-
- _req = None
- _usr = None
- _pwd = None
-
- def loadAccountInfo(self, name, req):
- self._req = req
- try:
- result = loads(self.runAuthQuery())
- except Exception:
- # todo: return or let it be thrown?
- return
-
- premium = False
- valid_untill = -1
-
- if "expire" in result.keys() and result["expire"]:
- premium = True
- valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result["expire"])).timetuple())
- traffic_left = result["balance"] * 1024
-
- return ({
- "validuntil": valid_untill,
- "trafficleft": traffic_left,
- "premium": premium
- })
-
- def login(self, user, data, req):
- self._usr = user
- self._pwd = hashlib.sha1(hashlib.md5(data["password"]).hexdigest()).hexdigest()
- self._req = req
-
- try:
- response = loads(self.runAuthQuery())
- except Exception:
- self.wrongPassword()
-
- if "errno" in response.keys():
- self.wrongPassword()
- data['usr'] = self._usr
- data['pwd'] = self._pwd
-
- def createAuthQuery(self):
- query = self._api_query
- query["username"] = self._usr
- query["password"] = self._pwd
-
- return query
-
- def runAuthQuery(self):
- data = self._req.load(self._api_url, post=self.createAuthQuery())
-
- return data \ No newline at end of file
diff --git a/module/plugins/accounts/NosuploadCom.py b/module/plugins/accounts/NosuploadCom.py
deleted file mode 100644
index e523ee2f4..000000000
--- a/module/plugins/accounts/NosuploadCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class NosuploadCom(XFSAccount):
- __name__ = "NosuploadCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Nosupload.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "nosupload.com"
diff --git a/module/plugins/accounts/NovafileCom.py b/module/plugins/accounts/NovafileCom.py
deleted file mode 100644
index ab61bf0fc..000000000
--- a/module/plugins/accounts/NovafileCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class NovafileCom(XFSAccount):
- __name__ = "NovafileCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Novafile.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "novafile.com"
diff --git a/module/plugins/accounts/NowVideoSx.py b/module/plugins/accounts/NowVideoSx.py
deleted file mode 100644
index 2f7b033bd..000000000
--- a/module/plugins/accounts/NowVideoSx.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class NowVideoSx(Account):
- __name__ = "NowVideoSx"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """NowVideo.at account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'>Your premium membership expires on: (.+?)<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = None
-
- html = req.load("http://www.nowvideo.sx/premium.php")
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%Y-%b-%d"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- else:
- premium = False
- validuntil = -1
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
-
- def login(self, user, data, req):
- html = req.load("http://www.nowvideo.sx/login.php",
- post={'user': user, 'pass': data['password']},
- decode=True)
-
- if re.search(r'>Log In<', html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/OboomCom.py b/module/plugins/accounts/OboomCom.py
deleted file mode 100644
index 020a45ba7..000000000
--- a/module/plugins/accounts/OboomCom.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-try:
- from beaker.crypto.pbkdf2 import PBKDF2
-
-except ImportError:
- from beaker.crypto.pbkdf2 import pbkdf2
- from binascii import b2a_hex
-
- class PBKDF2(object):
- def __init__(self, passphrase, salt, iterations=1000):
- self.passphrase = passphrase
- self.salt = salt
- self.iterations = iterations
-
- def hexread(self, octets):
- return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class OboomCom(Account):
- __name__ = "OboomCom"
- __type__ = "account"
- __version__ = "0.24"
-
- __description__ = """Oboom.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stanley", "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/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 userData['premium'] == "null":
- premium = False
- else:
- premium = True
-
- if userData['premium_unix'] == "null":
- validUntil = -1
- else:
- validUntil = float(userData['premium_unix'])
-
- traffic = userData['traffic']
-
- trafficLeft = traffic['current'] / 1024 #@TODO: Remove `/ 1024` in 0.4.10
- maxTraffic = traffic['max'] / 1024 #@TODO: Remove `/ 1024` in 0.4.10
-
- session = accountData['session']
-
- return {'premium' : premium,
- 'validuntil' : validUntil,
- 'trafficleft': trafficLeft,
- 'maxtraffic' : maxTraffic,
- 'session' : session}
-
-
- 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 b19e2bc69..000000000
--- a/module/plugins/accounts/OneFichierCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class OneFichierCom(Account):
- __name__ = "OneFichierCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """1fichier.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'Your Premium Status will end the (\d+/\d+/\d+)'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = None
-
- html = req.load("https://1fichier.com/console/abo.pl")
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1)
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%d/%m/%Y"))
- except Exception, e:
- self.logError(e)
- else:
- premium = True
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium or False}
-
-
- def login(self, user, data, req):
- req.http.c.setopt(pycurl.REFERER, "https://1fichier.com/login.pl?lg=en")
-
- html = req.load("https://1fichier.com/login.pl?lg=en",
- post={'mail' : user,
- 'pass' : data['password'],
- 'It' : "on",
- 'purge' : "off",
- 'valider': "Send"},
- decode=True)
-
- if '>Invalid email address' in html or '>Invalid password' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/OverLoadMe.py b/module/plugins/accounts/OverLoadMe.py
deleted file mode 100644
index 64d04aded..000000000
--- a/module/plugins/accounts/OverLoadMe.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 OverLoadMe(Account):
- __name__ = "OverLoadMe"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Over-Load.me account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("marley", "marley@over-load.me")]
-
-
- def loadAccountInfo(self, user, req):
- https = "https" if self.getConfig('ssl') else "http"
- data = self.getAccountData(user)
- html = req.load(https + "://api.over-load.me/account.php",
- get={'user': user,
- 'auth': data['password']}).strip()
-
- data = json_loads(html)
- self.logDebug(data)
-
- # Check for premium
- if data['membership'] == "Free":
- return {'premium': False, 'validuntil': None, 'trafficleft': None}
- else:
- return {'premium': True, 'validuntil': data['expirationunix'], 'trafficleft': -1}
-
-
- def login(self, user, data, req):
- https = "https" if self.getConfig('ssl') else "http"
- 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 c8ea2fa26..000000000
--- a/module/plugins/accounts/PremiumTo.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class PremiumTo(Account):
- __name__ = "PremiumTo"
- __type__ = "account"
- __version__ = "0.08"
-
- __description__ = """Premium.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- def loadAccountInfo(self, user, req):
- traffic = req.load("http://premium.to/api/straffic.php",
- get={'username': self.username, 'password': self.password})
-
- if "wrong username" not in traffic:
- trafficleft = sum(map(float, traffic.split(';'))) / 1024 #@TODO: Remove `/ 1024` in 0.4.10
- return {'premium': True, 'trafficleft': trafficleft, 'validuntil': -1}
- else:
- return {'premium': False, 'trafficleft': None, 'validuntil': None}
-
-
- def login(self, user, data, req):
- self.username = user
- self.password = data['password']
- authcode = req.load("http://premium.to/api/getauthcode.php",
- get={'username': user, 'password': self.password},
- decode=True)
-
- 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 7d061ec2d..000000000
--- a/module/plugins/accounts/PremiumizeMe.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 PremiumizeMe(Account):
- __name__ = "PremiumizeMe"
- __type__ = "account"
- __version__ = "0.13"
-
- __description__ = """Premiumize.me account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Florian Franzen", "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)} #@TODO: Remove `/ 1024` in 0.4.10
-
- 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",
- get={'method' : "accountstatus",
- 'params[login]': user,
- 'params[pass]' : self.getAccountData(user)['password']})
- return json_loads(answer)
diff --git a/module/plugins/accounts/PutdriveCom.py b/module/plugins/accounts/PutdriveCom.py
deleted file mode 100644
index 4f2fadbcc..000000000
--- a/module/plugins/accounts/PutdriveCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.accounts.ZeveraCom import ZeveraCom
-
-
-class PutdriveCom(ZeveraCom):
- __name__ = "PutdriveCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Putdrive.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "putdrive.com"
diff --git a/module/plugins/accounts/QuickshareCz.py b/module/plugins/accounts/QuickshareCz.py
deleted file mode 100644
index 16141d63e..000000000
--- a/module/plugins/accounts/QuickshareCz.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class QuickshareCz(Account):
- __name__ = "QuickshareCz"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Quickshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- TRAFFIC_LEFT_PATTERN = r'Stav kreditu: <strong>(.+?)</strong>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.quickshare.cz/premium", decode=True)
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- trafficleft = self.parseTraffic(m.group(1))
- 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 829e54a46..000000000
--- a/module/plugins/accounts/RPNetBiz.py
+++ /dev/null
@@ -1,51 +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.12"
-
- __description__ = """RPNet.biz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Dman", "dmanugm@gmail.com")]
-
-
- def loadAccountInfo(self, user, req):
- # Get account information from rpnet.biz
- res = self.getAccountStatus(user, req)
- try:
- if res['accountInfo']['isPremium']:
- # Parse account info. Change the trafficleft later to support per host info.
- account_info = {"validuntil": float(res['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
- res = self.getAccountStatus(user, req)
-
- # If we have an error in the res, we have wrong login information
- if 'error' in res:
- self.wrongPassword()
-
-
- def getAccountStatus(self, user, req):
- # Using the rpnet API, check if valid premium account
- res = req.load("https://premium.rpnet.biz/client_api.php",
- get={"username": user, "password": self.getAccountData(user)['password'],
- "action": "showAccountInformation"})
- self.logDebug("JSON data: %s" % res)
-
- return json_loads(res)
diff --git a/module/plugins/accounts/RapideoPl.py b/module/plugins/accounts/RapideoPl.py
deleted file mode 100644
index 3e9d52fe8..000000000
--- a/module/plugins/accounts/RapideoPl.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import datetime
-import hashlib
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads as loads
-
-
-class RapideoPl(Account):
- __name__ = "RapideoPl"
- __version__ = "0.01"
- __type__ = "account"
- __description__ = "Rapideo.pl account plugin"
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@rapideo.pl")]
-
- _api_url = "http://enc.rapideo.pl"
-
- _api_query = {
- "site": "newrd",
- "username": "",
- "password": "",
- "output": "json",
- "loc": "1",
- "info": "1"
- }
-
- _req = None
- _usr = None
- _pwd = None
-
- def loadAccountInfo(self, name, req):
- self._req = req
- try:
- result = loads(self.runAuthQuery())
- except Exception:
- # todo: return or let it be thrown?
- return
-
- premium = False
- valid_untill = -1
- if "expire" in result.keys() and result["expire"]:
- premium = True
- valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result["expire"])).timetuple())
-
- traffic_left = result["balance"]
-
- return ({
- "validuntil": valid_untill,
- "trafficleft": traffic_left,
- "premium": premium
- })
-
- def login(self, user, data, req):
- self._usr = user
- self._pwd = hashlib.md5(data["password"]).hexdigest()
- self._req = req
- try:
- response = loads(self.runAuthQuery())
- except Exception:
- self.wrongPassword()
-
- if "errno" in response.keys():
- self.wrongPassword()
- data['usr'] = self._usr
- data['pwd'] = self._pwd
-
- def createAuthQuery(self):
- query = self._api_query
- query["username"] = self._usr
- query["password"] = self._pwd
-
- return query
-
- def runAuthQuery(self):
- data = self._req.load(self._api_url, post=self.createAuthQuery())
-
- return data \ No newline at end of file
diff --git a/module/plugins/accounts/RapidfileshareNet.py b/module/plugins/accounts/RapidfileshareNet.py
deleted file mode 100644
index c0dd7eaee..000000000
--- a/module/plugins/accounts/RapidfileshareNet.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class RapidfileshareNet(XFSAccount):
- __name__ = "RapidfileshareNet"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Rapidfileshare.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "rapidfileshare.net"
-
- TRAFFIC_LEFT_PATTERN = r'>Traffic available today:</TD><TD><label for="name">\s*(?P<S>[\d.,]+)\s*(?:(?P<U>[\w^_]+))?'
diff --git a/module/plugins/accounts/RapidgatorNet.py b/module/plugins/accounts/RapidgatorNet.py
deleted file mode 100644
index 23d7aff53..000000000
--- a/module/plugins/accounts/RapidgatorNet.py
+++ /dev/null
@@ -1,72 +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.09"
-
- __description__ = """Rapidgator.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- API_URL = "http://rapidgator.net/api/user"
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = False
- sid = None
-
- try:
- sid = self.getAccountData(user).get('sid')
- assert sid
-
- html = req.load("%s/info" % self.API_URL, get={'sid': sid})
-
- self.logDebug("API:USERINFO", html)
-
- json = json_loads(html)
-
- if json['response_status'] == 200:
- if "reset_in" in json['response']:
- self.scheduleRefresh(user, json['response']['reset_in'])
-
- validuntil = json['response']['expire_date']
- trafficleft = float(json['response']['traffic_left']) / 1024 #@TODO: Remove `/ 1024` in 0.4.10
- premium = True
- else:
- self.logError(json['response_details'])
-
- except Exception, e:
- self.logError(e)
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium,
- 'sid' : sid}
-
-
- def login(self, user, data, req):
- try:
- html = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']})
-
- self.logDebug("API:LOGIN", html)
-
- json = json_loads(html)
-
- 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/RapiduNet.py b/module/plugins/accounts/RapiduNet.py
deleted file mode 100644
index 70f47b673..000000000
--- a/module/plugins/accounts/RapiduNet.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class RapiduNet(Account):
- __name__ = "RapiduNet"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Rapidu.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", None),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- PREMIUM_PATTERN = r'>Account: <b>Premium'
-
- VALID_UNTIL_PATTERN = r'>Account: <b>\w+ \((\d+)'
-
- TRAFFIC_LEFT_PATTERN = r'class="tipsyS"><b>(.+?)<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = -1
- premium = False
-
- html = req.load("https://rapidu.net/", decode=True)
-
- if re.search(self.PREMIUM_PATTERN, html):
- premium = True
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- validuntil = time.time() + (86400 * int(m.group(1)))
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- trafficleft = self.parseTraffic(m.group(1))
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
-
-
- def login(self, user, data, req):
- req.load("https://rapidu.net/ajax.php",
- get={'a': "getChangeLang"},
- post={'_go' : "",
- 'lang': "en"})
-
- json = json_loads(req.load("https://rapidu.net/ajax.php",
- get={'a': "getUserLogin"},
- post={'_go' : "",
- 'login' : user,
- 'pass' : data['password'],
- 'remember': "1"}))
-
- self.logDebug(json)
-
- if not json['message'] == "success":
- self.wrongPassword()
diff --git a/module/plugins/accounts/RarefileNet.py b/module/plugins/accounts/RarefileNet.py
deleted file mode 100644
index 577a6c8f6..000000000
--- a/module/plugins/accounts/RarefileNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class RarefileNet(XFSAccount):
- __name__ = "RarefileNet"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """RareFile.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "rarefile.net"
diff --git a/module/plugins/accounts/RealdebridCom.py b/module/plugins/accounts/RealdebridCom.py
deleted file mode 100644
index 41d8a0975..000000000
--- a/module/plugins/accounts/RealdebridCom.py
+++ /dev/null
@@ -1,40 +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.45"
-
- __description__ = """Real-Debrid.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
-
-
- def loadAccountInfo(self, user, req):
- if self.pin_code:
- return {"premium": False}
- html = req.load("https://real-debrid.com/api/account.php")
- xml = dom.parseString(html)
- account_info = {"validuntil": float(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
- "trafficleft": -1}
-
- return account_info
-
-
- def login(self, user, data, req):
- self.pin_code = False
- html = req.load("https://real-debrid.com/ajax/login.php",
- get={"user": user, "pass": data['password']},
- decode=True)
-
- if "Your login informations are incorrect" in html:
- self.wrongPassword()
-
- elif "PIN Code required" in html:
- 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 04e71c9ad..000000000
--- a/module/plugins/accounts/RehostTo.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class RehostTo(Account):
- __name__ = "RehostTo"
- __type__ = "account"
- __version__ = "0.16"
-
- __description__ = """Rehost.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- trafficleft = None
- validuntil = -1
- session = ""
-
- html = req.load("http://rehost.to/api.php",
- get={'cmd' : "login", 'user': user,
- 'pass': self.getAccountData(user)['password']})
- try:
- session = html.split(",")[1].split("=")[1]
-
- html = req.load("http://rehost.to/api.php",
- get={'cmd': "get_premium_credits", 'long_ses': session})
-
- if html.strip() == "0,0" or "ERROR" in html:
- self.logDebug(html)
- else:
- traffic, valid = html.split(",")
-
- premium = True
- trafficleft = self.parseTraffic(traffic + "MB")
- validuntil = float(valid)
-
- finally:
- return {'premium' : premium,
- 'trafficleft': trafficleft,
- 'validuntil' : validuntil,
- 'session' : session}
-
-
- def login(self, user, data, req):
- html = req.load("http://rehost.to/api.php",
- get={'cmd': "login", 'user': user, 'pass': data['password']},
- decode=True)
-
- if "ERROR" in html:
- self.logDebug(html)
- self.wrongPassword()
diff --git a/module/plugins/accounts/RyushareCom.py b/module/plugins/accounts/RyushareCom.py
deleted file mode 100644
index 8c56ff20f..000000000
--- a/module/plugins/accounts/RyushareCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class RyushareCom(XFSAccount):
- __name__ = "RyushareCom"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """Ryushare.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "ryushare.com"
diff --git a/module/plugins/accounts/SafesharingEu.py b/module/plugins/accounts/SafesharingEu.py
deleted file mode 100644
index 2e58d33b3..000000000
--- a/module/plugins/accounts/SafesharingEu.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class SafesharingEu(XFSAccount):
- __name__ = "SafesharingEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Safesharing.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "safesharing.eu"
diff --git a/module/plugins/accounts/SecureUploadEu.py b/module/plugins/accounts/SecureUploadEu.py
deleted file mode 100644
index b335c94da..000000000
--- a/module/plugins/accounts/SecureUploadEu.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class SecureUploadEu(XFSAccount):
- __name__ = "SecureUploadEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """SecureUpload.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "secureupload.eu"
diff --git a/module/plugins/accounts/SendmywayCom.py b/module/plugins/accounts/SendmywayCom.py
deleted file mode 100644
index 4fcbe0b7a..000000000
--- a/module/plugins/accounts/SendmywayCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class SendmywayCom(XFSAccount):
- __name__ = "SendmywayCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Sendmyway.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "sendmyway.com"
diff --git a/module/plugins/accounts/ShareonlineBiz.py b/module/plugins/accounts/ShareonlineBiz.py
deleted file mode 100644
index 4214b4401..000000000
--- a/module/plugins/accounts/ShareonlineBiz.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class ShareonlineBiz(Account):
- __name__ = "ShareonlineBiz"
- __type__ = "account"
- __version__ = "0.33"
-
- __description__ = """Share-online.biz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def api_response(self, user, req):
- return req.load("http://api.share-online.biz/cgi-bin",
- get={'q' : "userdetails",
- 'aux' : "traffic",
- 'username': user,
- 'password': self.getAccountData(user)['password']})
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = None
- trafficleft = -1
- maxtraffic = 100 * 1024 * 1024 * 1024 #: 100 GB
-
- api = {}
- for line in self.api_response(user, req).splitlines():
- if "=" in line:
- key, value = line.split("=")
- api[key] = value
-
- self.logDebug(api)
-
- if api['a'].lower() != "not_available":
- req.cj.setCookie("share-online.biz", 'a', api['a'])
-
- premium = api['group'] in ("PrePaid", "Premium", "Penalty-Premium")
-
- validuntil = float(api['expire_date'])
-
- traffic = float(api['traffic_1d'].split(";")[0])
-
- if maxtraffic > traffic:
- trafficleft = maxtraffic - traffic
- else:
- trafficleft = -1
-
- maxtraffic /= 1024 #@TODO: Remove `/ 1024` in 0.4.10
- trafficleft /= 1024 #@TODO: Remove `/ 1024` in 0.4.10
-
- return {'premium' : premium,
- 'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'maxtraffic' : maxtraffic}
-
-
- def login(self, user, data, req):
- html = self.api_response(user, req)
- err = re.search(r'\*\*(.+?)\*\*', html)
- if err:
- self.logError(err.group(1))
- self.wrongPassword()
diff --git a/module/plugins/accounts/SimplyPremiumCom.py b/module/plugins/accounts/SimplyPremiumCom.py
deleted file mode 100644
index 8caf600f9..000000000
--- a/module/plugins/accounts/SimplyPremiumCom.py
+++ /dev/null
@@ -1,48 +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.05"
-
- __description__ = """Simply-Premium.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
-
-
- def loadAccountInfo(self, user, req):
- premium = False
- validuntil = -1
- trafficleft = None
-
- json_data = req.load('http://www.simply-premium.com/api/user.php?format=json')
-
- self.logDebug("JSON data: %s" % json_data)
-
- json_data = json_loads(json_data)
-
- if 'vip' in json_data['result'] and json_data['result']['vip']:
- premium = True
-
- if 'timeend' in json_data['result'] and json_data['result']['timeend']:
- validuntil = float(json_data['result']['timeend'])
-
- if 'remain_traffic' in json_data['result'] and json_data['result']['remain_traffic']:
- trafficleft = float(json_data['result']['remain_traffic']) / 1024 #@TODO: Remove `/ 1024` in 0.4.10
-
- return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
-
-
- def login(self, user, data, req):
- req.cj.setCookie("simply-premium.com", "lang", "EN")
-
- html = req.load("http://www.simply-premium.com/login.php",
- post={'key': user} if not data['password'] else {'login_name': user, 'login_pass': data['password']},
- decode=True)
-
- 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 24108eb0b..000000000
--- a/module/plugins/accounts/SimplydebridCom.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Account import Account
-
-
-class SimplydebridCom(Account):
- __name__ = "SimplydebridCom"
- __type__ = "account"
- __version__ = "0.11"
-
- __description__ = """Simply-Debrid.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def loadAccountInfo(self, user, req):
- get_data = {'login': 2, 'u': self.loginname, 'p': self.password}
- res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
- data = [x.strip() for x in res.split(";")]
- if str(data[0]) != "1":
- return {"premium": False}
- else:
- return {"trafficleft": -1, "validuntil": time.mktime(time.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}
-
- res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
- if res != "02: loggin success":
- self.wrongPassword()
diff --git a/module/plugins/accounts/SmoozedCom.py b/module/plugins/accounts/SmoozedCom.py
deleted file mode 100644
index 1c303331b..000000000
--- a/module/plugins/accounts/SmoozedCom.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import hashlib
-import time
-
-try:
- from beaker.crypto.pbkdf2 import PBKDF2
-
-except ImportError:
- from beaker.crypto.pbkdf2 import pbkdf2
- from binascii import b2a_hex
-
- class PBKDF2(object):
- def __init__(self, passphrase, salt, iterations=1000):
- self.passphrase = passphrase
- self.salt = salt
- self.iterations = iterations
-
- def hexread(self, octets):
- return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class SmoozedCom(Account):
- __name__ = "SmoozedCom"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """Smoozed.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("", "")]
-
-
- def loadAccountInfo(self, user, req):
- status = self.getAccountStatus(user, req)
-
- self.logDebug(status)
-
- if status['state'] != 'ok':
- info = {'validuntil' : None,
- 'trafficleft': None,
- 'premium' : False}
- else:
- # Parse account info
- info = {'validuntil' : float(status["data"]["user"]["user_premium"]),
- 'trafficleft': max(0, status["data"]["traffic"][1] - status["data"]["traffic"][0]),
- 'session' : status["data"]["session_key"],
- 'hosters' : [hoster["name"] for hoster in status["data"]["hoster"]]}
-
- if info['validuntil'] < time.time():
- if float(status["data"]["user"].get("user_trial", 0)) > time.time():
- info['premium'] = True
- else:
- info['premium'] = False
- else:
- info['premium'] = True
-
- return 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['state'] != 'ok':
- self.wrongPassword()
-
-
- def getAccountStatus(self, user, req):
- password = self.getAccountData(user)['password']
- salt = hashlib.sha256(password).hexdigest()
- encrypted = PBKDF2(password, salt, iterations=1000).hexread(32)
-
- return json_loads(req.load("http://www2.smoozed.com/api/login",
- get={'auth': user, 'password': encrypted}))
diff --git a/module/plugins/accounts/StahnuTo.py b/module/plugins/accounts/StahnuTo.py
deleted file mode 100644
index 882dbd2c3..000000000
--- a/module/plugins/accounts/StahnuTo.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class StahnuTo(Account):
- __name__ = "StahnuTo"
- __type__ = "account"
- __version__ = "0.05"
-
- __description__ = """StahnuTo account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.stahnu.to/")
-
- m = re.search(r'>VIP: (\d+.*)<', html)
- trafficleft = self.parseTraffic(m.group(1)) if m else 0
-
- return {"premium": trafficleft > 512, "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"},
- decode=True)
-
- if not '<a href="logout.php">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/StreamcloudEu.py b/module/plugins/accounts/StreamcloudEu.py
deleted file mode 100644
index aa1eafcbd..000000000
--- a/module/plugins/accounts/StreamcloudEu.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class StreamcloudEu(XFSAccount):
- __name__ = "StreamcloudEu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Streamcloud.eu account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "streamcloud.eu"
diff --git a/module/plugins/accounts/TurbobitNet.py b/module/plugins/accounts/TurbobitNet.py
deleted file mode 100644
index 010fbc270..000000000
--- a/module/plugins/accounts/TurbobitNet.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class TurbobitNet(Account):
- __name__ = "TurbobitNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """TurbobitNet account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://turbobit.net")
-
- m = re.search(r'<u>Turbo Access</u> to ([\d.]+)', html)
- if m:
- premium = True
- validuntil = time.mktime(time.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"},
- decode=True)
-
- if not '<div class="menu-item user-name">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/TusfilesNet.py b/module/plugins/accounts/TusfilesNet.py
deleted file mode 100644
index d06ba0583..000000000
--- a/module/plugins/accounts/TusfilesNet.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class TusfilesNet(XFSAccount):
- __name__ = "TusfilesNet"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """ Tusfile.net account plugin """
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- HOSTER_DOMAIN = "tusfiles.net"
-
- VALID_UNTIL_PATTERN = r'<span class="label label-default">([^<]+)</span>'
- TRAFFIC_LEFT_PATTERN = r'<td><img src="//www\.tusfiles\.net/i/icon/meter\.png" alt=""/></td>\n<td>&nbsp;(?P<S>[\d.,]+)'
diff --git a/module/plugins/accounts/UlozTo.py b/module/plugins/accounts/UlozTo.py
deleted file mode 100644
index f95c8834b..000000000
--- a/module/plugins/accounts/UlozTo.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.Account import Account
-
-
-class UlozTo(Account):
- __name__ = "UlozTo"
- __type__ = "account"
- __version__ = "0.10"
-
- __description__ = """Uloz.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("pulpe", None)]
-
-
- TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a .*?title=".+?GB = ([\d.]+) MB"'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.ulozto.net/", decode=True)
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
-
- trafficleft = float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048 if m else 0
- premium = True if trafficleft else False
-
- return {'validuntil': -1, 'trafficleft': trafficleft, 'premium': premium}
-
-
- 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(urlparse.urljoin("http://www.ulozto.net/", action),
- post={'_token_' : token,
- 'do' : "loginForm-submit",
- 'login' : u"Přihlásit",
- 'password': data['password'],
- 'username': user,
- 'remember': "on"},
- decode=True)
-
- if '<div class="flash error">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadableCh.py b/module/plugins/accounts/UploadableCh.py
deleted file mode 100644
index 86ae5dd17..000000000
--- a/module/plugins/accounts/UploadableCh.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class UploadableCh(Account):
- __name__ = "UploadableCh"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Uploadable.ch account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Sasch", "gsasch@gmail.com")]
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.uploadable.ch/login.php")
-
- premium = '<a href="/logout.php"' in html
- trafficleft = -1 if premium else None
-
- return {'validuntil': None, 'trafficleft': trafficleft, 'premium': premium} #@TODO: validuntil
-
-
- def login(self, user, data, req):
- html = req.load("http://www.uploadable.ch/login.php",
- post={'userName' : user,
- 'userPassword' : data["password"],
- 'autoLogin' : "1",
- 'action__login': "normalLogin"},
- decode=True)
-
- if "Login failed" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadcCom.py b/module/plugins/accounts/UploadcCom.py
deleted file mode 100644
index d1e1a2ead..000000000
--- a/module/plugins/accounts/UploadcCom.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class UploadcCom(XFSAccount):
- __name__ = "UploadcCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Uploadc.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "uploadc.com"
diff --git a/module/plugins/accounts/UploadedTo.py b/module/plugins/accounts/UploadedTo.py
deleted file mode 100644
index d1556b6db..000000000
--- a/module/plugins/accounts/UploadedTo.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class UploadedTo(Account):
- __name__ = "UploadedTo"
- __type__ = "account"
- __version__ = "0.30"
-
- __description__ = """Uploaded.to account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- PREMIUM_PATTERN = r'<em>Premium</em>'
- VALID_UNTIL_PATTERN = r'<td>Duration:</td>\s*<th>(.+?)<'
- TRAFFIC_LEFT_PATTERN = r'<b class="cB">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = None
-
- html = req.load("http://uploaded.net/me")
-
- premium = True if re.search(self.PREMIUM_PATTERN, html) else False
-
- m = re.search(self.VALID_UNTIL_PATTERN, html, re.M)
- if m:
- expiredate = m.group(1).lower().strip()
-
- if expiredate == "unlimited":
- validuntil = -1
- else:
- m = re.findall(r'(\d+) (week|day|hour)', expiredate)
- if m:
- validuntil = time.time()
- for n, u in m:
- validuntil += float(n) * 60 * 60 * {'week': 168, 'day': 24, 'hour': 1}[u]
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- traffic = m.groupdict()
- size = traffic['S'].replace('.', '')
- unit = traffic['U'].lower()
-
- if unit.startswith('t'): #@NOTE: Remove in 0.4.10
- trafficleft = float(size.replace(',', '.')) / 1024
- trafficleft *= 1 << 40
- else:
- trafficleft = self.parseTraffic(size + unit)
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- # req.cj.setCookie("uploaded.net", "lang", "en")
-
- html = req.load("http://uploaded.net/io/login",
- post={'id': user, 'pw': data['password'], '_': ""},
- decode=True)
-
- if '"err"' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadheroCom.py b/module/plugins/accounts/UploadheroCom.py
deleted file mode 100644
index 714f5b0a6..000000000
--- a/module/plugins/accounts/UploadheroCom.py
+++ /dev/null
@@ -1,42 +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.21"
-
- __description__ = """Uploadhero.co account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mcmyst", "mcmyst@hotmail.fr")]
-
-
- def loadAccountInfo(self, user, req):
- premium_pattern = re.compile('Il vous reste <span class="bleu">(\d+)</span> jours premium')
-
- data = self.getAccountData(user)
- html = req.load("http://uploadhero.co/my-account")
-
- if premium_pattern.search(html):
- end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(html).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):
- html = req.load("http://uploadhero.co/lib/connexion.php",
- post={"pseudo_login": user, "password_login": data['password']},
- decode=True)
-
- if "mot de passe invalide" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadingCom.py b/module/plugins/accounts/UploadingCom.py
deleted file mode 100644
index a20c44535..000000000
--- a/module/plugins/accounts/UploadingCom.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-from module.plugins.internal.SimpleHoster import set_cookies
-
-
-class UploadingCom(Account):
- __name__ = "UploadingCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """Uploading.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
- PREMIUM_PATTERN = r'UPGRADE TO PREMIUM'
- VALID_UNTIL_PATTERN = r'Valid Until:(.+?)<'
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = None
-
- html = req.load("http://uploading.com/")
-
- premium = False if re.search(self.PREMIUM_PATTERN, html) else True
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%b %d, %Y"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- else:
- premium = False
- validuntil = None
-
- return {'validuntil' : validuntil,
- 'trafficleft': trafficleft,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- set_cookies(req.cj,
- [("uploading.com", "lang" , "1" ),
- ("uploading.com", "language", "1" ),
- ("uploading.com", "setlang" , "en"),
- ("uploading.com", "_lang" , "en")])
-
- req.load("http://uploading.com/")
- req.load("http://uploading.com/general/login_form/?JsHttpRequest=%s-xml" % long(time.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 c40dbd6e6..000000000
--- a/module/plugins/accounts/UptoboxCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class UptoboxCom(XFSAccount):
- __name__ = "UptoboxCom"
- __type__ = "account"
- __version__ = "0.08"
-
- __description__ = """DDLStorage.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "uptobox.com"
- HOSTER_URL = "https://uptobox.com/"
- LOGIN_URL = "https://login.uptobox.com/"
diff --git a/module/plugins/accounts/VidPlayNet.py b/module/plugins/accounts/VidPlayNet.py
deleted file mode 100644
index 5bfc24963..000000000
--- a/module/plugins/accounts/VidPlayNet.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class VidPlayNet(XFSAccount):
- __name__ = "VidPlayNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """VidPlay.net account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "vidplay.net"
diff --git a/module/plugins/accounts/WebshareCz.py b/module/plugins/accounts/WebshareCz.py
deleted file mode 100644
index f032e2317..000000000
--- a/module/plugins/accounts/WebshareCz.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import hashlib
-import re
-import time
-
-from passlib.hash import md5_crypt
-
-from module.plugins.Account import Account
-
-
-class WebshareCz(Account):
- __name__ = "WebshareCz"
- __type__ = "account"
- __version__ = "0.07"
-
- __description__ = """Webshare.cz account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("rush", "radek.senfeld@gmail.com")]
-
-
- VALID_UNTIL_PATTERN = r'<vip_until>(.+)</vip_until>'
-
- TRAFFIC_LEFT_PATTERN = r'<bytes>(.+)</bytes>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("https://webshare.cz/api/user_data/",
- post={'wst': self.infos['wst']},
- decode=True)
-
- self.logDebug("Response: " + html)
-
- expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
- self.logDebug("Expire date: " + expiredate)
-
- validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
- trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
- premium = validuntil > time.time()
-
- return {'validuntil': validuntil, 'trafficleft': -1, 'premium': premium}
-
-
- def login(self, user, data, req):
- salt = req.load("https://webshare.cz/api/salt/",
- post={'username_or_email': user,
- 'wst' : ""},
- decode=True)
-
- if "<status>OK</status>" not in salt:
- self.wrongPassword()
-
- salt = re.search('<salt>(.+)</salt>', salt).group(1)
- password = hashlib.sha1(md5_crypt.encrypt(data["password"], salt=salt)).hexdigest()
- digest = hashlib.md5(user + ":Webshare:" + password).hexdigest()
-
- login = req.load("https://webshare.cz/api/login/",
- post={'digest' : digest,
- 'keep_logged_in' : 1,
- 'password' : password,
- 'username_or_email': user,
- 'wst' : ""},
- decode=True)
-
- if "<status>OK</status>" not in login:
- self.wrongPassword()
-
- self.infos['wst'] = re.search('<token>(.+)</token>', login).group(1)
diff --git a/module/plugins/accounts/XFileSharingPro.py b/module/plugins/accounts/XFileSharingPro.py
deleted file mode 100644
index 8dc7f3a30..000000000
--- a/module/plugins/accounts/XFileSharingPro.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSAccount import XFSAccount
-
-
-class XFileSharingPro(XFSAccount):
- __name__ = "XFileSharingPro"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """XFileSharingPro multi-purpose account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = None
-
-
- def init(self):
- if self.HOSTER_DOMAIN:
- return super(XFileSharingPro, self).init()
-
-
- def loadAccountInfo(self, user, req):
- return super(XFileSharingPro if self.HOSTER_DOMAIN else XFSAccount, self).loadAccountInfo(user, req)
-
-
- def login(self, user, data, req):
- if self.HOSTER_DOMAIN:
- try:
- return super(XFileSharingPro, self).login(user, data, req)
- except Exception:
- self.HOSTER_URL = self.HOSTER_URL.replace("www.", "")
- return super(XFileSharingPro, self).login(user, data, req)
diff --git a/module/plugins/accounts/YibaishiwuCom.py b/module/plugins/accounts/YibaishiwuCom.py
deleted file mode 100644
index 33602a2fe..000000000
--- a/module/plugins/accounts/YibaishiwuCom.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class YibaishiwuCom(Account):
- __name__ = "YibaishiwuCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """115.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 9bc6d0487..000000000
--- a/module/plugins/accounts/ZeveraCom.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Account import Account
-
-
-class ZeveraCom(Account):
- __name__ = "ZeveraCom"
- __type__ = "account"
- __version__ = "0.26"
-
- __description__ = """Zevera.com account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = "zevera.com"
-
-
- def __init__(self, manager, accounts): #@TODO: remove in 0.4.10
- self.init()
- return super(ZeveraCom, self).__init__(manager, accounts)
-
-
- def init(self):
- if not self.HOSTER_DOMAIN:
- self.logError(_("Missing HOSTER_DOMAIN"))
-
- if not hasattr(self, "API_URL"):
- self.API_URL = "http://api.%s/jDownloader.ashx" % (self.HOSTER_DOMAIN or "")
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- premium = False
-
- api = self.api_response(req)
-
- if "No trafic" not in api and api['endsubscriptiondate'] != "Expired!":
- validuntil = time.mktime(time.strptime(api['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S"))
- trafficleft = float(api['availabletodaytraffic']) * 1024 if api['orondaytrafficlimit'] != '0' else -1
- premium = True
-
- return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
-
-
- def login(self, user, data, req):
- self.user = user
- self.password = data['password']
-
- if self.api_response(req) == "No trafic":
- self.wrongPassword()
-
-
- def api_response(self, req, just_header=False, **kwargs):
- get_data = {'cmd' : "accountinfo",
- 'login': self.user,
- 'pass' : self.password}
-
- get_data.update(kwargs)
-
- res = req.load(self.API_URL,
- get=get_data,
- just_header=just_header,
- decode=True)
-
- self.logDebug(res)
-
- if ':' in res:
- if not just_header:
- res = res.replace(',', '\n')
- return dict((y.strip().lower(), z.strip()) for (y, z) in
- [x.split(':', 1) for x in res.splitlines() if ':' in x])
- else:
- return res
diff --git a/module/plugins/accounts/__init__.py b/module/plugins/accounts/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/accounts/__init__.py
+++ /dev/null
diff --git a/module/plugins/captcha/GigasizeCom.py b/module/plugins/captcha/GigasizeCom.py
deleted file mode 100644
index 52c41729b..000000000
--- a/module/plugins/captcha/GigasizeCom.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha.OCR import OCR
-
-
-class GigasizeCom(OCR):
- __name__ = "GigasizeCom"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Gigasize.com ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "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 95b107977..000000000
--- a/module/plugins/captcha/LinksaveIn.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 -*-
-
-try:
- from PIL import Image
-except ImportError:
- import Image
-
-import glob
-import os
-
-from module.plugins.captcha.OCR import OCR
-
-
-class LinksaveIn(OCR):
- __name__ = "LinksaveIn"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Linksave.in ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- def __init__(self):
- OCR.__init__(self)
- self.data_dir = os.path.dirname(os.path.abspath(__file__)) + os.sep + "LinksaveIn" + os.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.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 Exception:
- cstat[rgb_c] = 1
- if rgb_bg == rgb_c:
- stat[bgpath] += 1
- max_p = 0
- bg = ""
- for bgpath, value in stat.iteritems():
- 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 1fb258c47..000000000
--- a/module/plugins/captcha/NetloadIn.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha.OCR import OCR
-
-
-class NetloadIn(OCR):
- __name__ = "NetloadIn"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Netload.in ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "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/OCR.py b/module/plugins/captcha/OCR.py
deleted file mode 100644
index 1874ba07d..000000000
--- a/module/plugins/captcha/OCR.py
+++ /dev/null
@@ -1,319 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-try:
- from PIL import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
-
-except ImportError:
- import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
-
-import logging
-import os
-import subprocess
-#import tempfile
-
-from module.utils import save_join
-
-
-class OCR(object):
- __name__ = "OCR"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """OCR base plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "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):
- #tmpTif = tempfile.NamedTemporaryFile(suffix=".tif")
- try:
- tmpTif = open(save_join("tmp", "tmpTif_%s.tif" % self.__name__), "wb")
- tmpTif.close()
-
- #tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
- tmpTxt = open(save_join("tmp", "tmpTxt_%s.txt" % self.__name__), "wb")
- tmpTxt.close()
-
- except IOError, e:
- self.logError(e)
- return
-
- self.logger.debug("save tiff")
- self.image.save(tmpTif.name, 'TIFF')
-
- if os.name == "nt":
- tessparams = [os.path.join(pypath, "tesseract", "tesseract.exe")]
- else:
- tessparams = ["tesseract"]
-
- tessparams.extend( [os.path.abspath(tmpTif.name), os.path.abspath(tmpTxt.name).replace(".txt", "")] )
-
- if subset and (digits or lowercase or uppercase):
- #tmpSub = tempfile.NamedTemporaryFile(suffix=".subset")
- with open(save_join("tmp", "tmpSub_%s.subset" % self.__name__), "wb") as tmpSub:
- 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(os.path.abspath(tmpSub.name))
-
- 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 Exception:
- self.result_captcha = ""
-
- self.logger.debug(self.result_captcha)
- try:
- os.remove(tmpTif.name)
- os.remove(tmpTxt.name)
- if subset and (digits or lowercase or uppercase):
- os.remove(tmpSub.name)
- except Exception:
- 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 Exception:
- 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/captcha/ShareonlineBiz.py b/module/plugins/captcha/ShareonlineBiz.py
deleted file mode 100644
index 6fad66600..000000000
--- a/module/plugins/captcha/ShareonlineBiz.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha.OCR import OCR
-
-
-class ShareonlineBiz(OCR):
- __name__ = "ShareonlineBiz"
- __type__ = "ocr"
- __version__ = "0.11"
-
- __description__ = """Shareonline.biz ocr plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "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/__init__.py b/module/plugins/captcha/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/captcha/__init__.py
+++ /dev/null
diff --git a/module/plugins/container/CCF.py b/module/plugins/container/CCF.py
deleted file mode 100644
index 235d5dc1d..000000000
--- a/module/plugins/container/CCF.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import MultipartPostHandler
-import re
-import urllib2
-
-from module.plugins.Container import Container
-from module.utils import fs_encode, save_join
-
-
-class CCF(Container):
- __name__ = "CCF"
- __type__ = "container"
- __version__ = "0.23"
-
- __pattern__ = r'.+\.ccf$'
-
- __description__ = """CCF container decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Willnix", "Willnix@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def decrypt(self, pyfile):
- fs_filename = fs_encode(pyfile.url.strip())
- opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
-
- dlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php',
- {'src' : "ccf",
- 'filename': "test.ccf",
- 'upload' : open(fs_filename, "rb")}).read()
-
- download_folder = self.config['general']['download_folder']
- dlc_file = save_join(download_folder, "tmp_%s.dlc" % pyfile.name)
-
- try:
- dlc = re.search(r'<dlc>(.+)</dlc>', dlc_content, re.S).group(1).decode('base64')
-
- except AttributeError:
- self.fail(_("Container is corrupted"))
-
- with open(dlc_file, "w") as tempdlc:
- tempdlc.write(dlc)
-
- self.urls = [dlc_file]
diff --git a/module/plugins/container/DLC.py b/module/plugins/container/DLC.py
deleted file mode 100644
index ff2f0104a..000000000
--- a/module/plugins/container/DLC.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-import xml.dom.minidom
-
-from Crypto.Cipher import AES
-
-from module.plugins.Container import Container
-from module.utils import decode, fs_encode
-
-
-class DLC(Container):
- __name__ = "DLC"
- __type__ = "container"
- __version__ = "0.24"
-
- __pattern__ = r'.+\.dlc$'
-
- __description__ = """DLC container decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("Schnusch", "Schnusch@users.noreply.github.com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- KEY = "cb99b5cbc24db398"
- IV = "9bc24cb995cb8db3"
- API_URL = "http://service.jdownloader.org/dlcrypt/service.php?srcType=dlc&destType=pylo&data=%s"
-
-
- def decrypt(self, pyfile):
- fs_filename = fs_encode(pyfile.url.strip())
- with open(fs_filename) as dlc:
- data = dlc.read().strip()
-
- data += '=' * (-len(data) % 4)
-
- dlc_key = data[-88:]
- dlc_data = data[:-88].decode('base64')
- dlc_content = self.load(self.API_URL % dlc_key)
-
- try:
- rc = re.search(r'<rc>(.+)</rc>', dlc_content, re.S).group(1).decode('base64')
-
- except AttributeError:
- self.fail(_("Container is corrupted"))
-
- key = iv = AES.new(self.KEY, AES.MODE_CBC, self.IV).decrypt(rc)
-
- self.data = AES.new(key, AES.MODE_CBC, iv).decrypt(dlc_data).decode('base64')
- self.packages = [(name or pyfile.name, links, name or pyfile.name) \
- for name, links in self.getPackages()]
-
-
- def getPackages(self):
- root = xml.dom.minidom.parseString(self.data).documentElement
- content = root.getElementsByTagName("content")[0]
- return self.parsePackages(content)
-
-
- def parsePackages(self, startNode):
- return [(decode(node.getAttribute("name")).decode('base64'), self.parseLinks(node)) \
- for node in startNode.getElementsByTagName("package")]
-
-
- def parseLinks(self, startNode):
- return [node.getElementsByTagName("url")[0].firstChild.data.decode('base64') \
- for node in startNode.getElementsByTagName("file")]
diff --git a/module/plugins/container/RSDF.py b/module/plugins/container/RSDF.py
deleted file mode 100644
index dd2d14cf7..000000000
--- a/module/plugins/container/RSDF.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import binascii
-import re
-
-from Crypto.Cipher import AES
-
-from module.plugins.Container import Container
-from module.utils import fs_encode
-
-
-class RSDF(Container):
- __name__ = "RSDF"
- __type__ = "container"
- __version__ = "0.29"
-
- __pattern__ = r'.+\.rsdf$'
-
- __description__ = """RSDF container decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("spoob", "spoob@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- KEY = "8C35192D964DC3182C6F84F3252239EB4A320D2500000000"
- IV = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
-
-
- def decrypt(self, pyfile):
- KEY = binascii.unhexlify(self.KEY)
- IV = binascii.unhexlify(self.IV)
-
- iv = AES.new(KEY, AES.MODE_ECB).encrypt(IV)
- cipher = AES.new(KEY, AES.MODE_CFB, iv)
-
- try:
- fs_filename = fs_encode(pyfile.url.strip())
- with open(fs_filename, 'r') as rsdf:
- data = rsdf.read()
-
- except IOError, e:
- self.fail(e)
-
- if re.search(r"<title>404 - Not Found</title>", data):
- pyfile.setStatus("offline")
-
- else:
- try:
- raw_links = binascii.unhexlify(''.join(data.split())).splitlines()
-
- except TypeError:
- self.fail(_("Container is corrupted"))
-
- for link in raw_links:
- if not link:
- continue
- link = cipher.decrypt(link.decode('base64')).replace('CCF: ', '')
- self.urls.append(link)
diff --git a/module/plugins/container/TXT.py b/module/plugins/container/TXT.py
deleted file mode 100644
index d419ee060..000000000
--- a/module/plugins/container/TXT.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import codecs
-
-from module.plugins.Container import Container
-from module.utils import fs_encode
-
-
-class TXT(Container):
- __name__ = "TXT"
- __type__ = "container"
- __version__ = "0.15"
-
- __pattern__ = r'.+\.(txt|text)$'
- __config__ = [("flush" , "bool" , "Flush list after adding", False ),
- ("encoding", "string", "File encoding" , "utf-8")]
-
- __description__ = """Read link lists in plain text formats"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("jeix", "jeix@hasnomail.com")]
-
-
- def decrypt(self, pyfile):
- try:
- encoding = codecs.lookup(self.getConfig('encoding')).name
-
- except Exception:
- encoding = "utf-8"
-
- fs_filename = fs_encode(pyfile.url.strip())
- txt = codecs.open(fs_filename, 'r', encoding)
- curPack = "Parsed links from %s" % pyfile.name
- packages = {curPack:[],}
-
- for link in txt.readlines():
- 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
- for key, value in packages.iteritems():
- if not value:
- packages.pop(key, None)
-
- if self.getConfig('flush'):
- try:
- txt = open(fs_filename, 'wb')
- txt.close()
-
- except IOError:
- self.logWarning(_("Failed to flush list"))
-
- for name, links in packages.iteritems():
- self.packages.append((name, links, name))
diff --git a/module/plugins/container/__init__.py b/module/plugins/container/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/container/__init__.py
+++ /dev/null
diff --git a/module/plugins/crypter/BitshareComFolder.py b/module/plugins/crypter/BitshareComFolder.py
deleted file mode 100644
index 256c5b5aa..000000000
--- a/module/plugins/crypter/BitshareComFolder.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class BitshareComFolder(SimpleCrypter):
- __name__ = "BitshareComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?bitshare\.com/\?d=\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Bitshare.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a href="(http://bitshare\.com/files/.+)">.+</a></td>'
- NAME_PATTERN = r'View public folder "(?P<N>.+)"</h1>'
-
-
-getInfo = create_getInfo(BitshareComFolder)
diff --git a/module/plugins/crypter/C1NeonCom.py b/module/plugins/crypter/C1NeonCom.py
deleted file mode 100644
index 926633ff7..000000000
--- a/module/plugins/crypter/C1NeonCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class C1NeonCom(DeadCrypter):
- __name__ = "C1NeonCom"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?c1neon\.com/.+'
- __config__ = []
-
- __description__ = """C1neon.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
-getInfo = create_getInfo(C1NeonCom)
diff --git a/module/plugins/crypter/ChipDe.py b/module/plugins/crypter/ChipDe.py
deleted file mode 100644
index f535da48d..000000000
--- a/module/plugins/crypter/ChipDe.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class ChipDe(Crypter):
- __name__ = "ChipDe"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?chip\.de/video/.+\.html'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Chip.de decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("4Christopher", "4Christopher@gmx.de")]
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url)
- try:
- f = re.search(r'"(http://video\.chip\.de/.+)"', self.html)
- except Exception:
- 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/CloudzillaToFolder.py b/module/plugins/crypter/CloudzillaToFolder.py
deleted file mode 100644
index 96d7245f1..000000000
--- a/module/plugins/crypter/CloudzillaToFolder.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class CloudzillaToFolder(SimpleHoster):
- __name__ = "CloudzillaToFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?cloudzilla\.to/share/folder/(?P<ID>[\w^_]+)'
-
- __description__ = """Cloudzilla.to folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'<span class="name" title="(?P<N>.+?)"'
- OFFLINE_PATTERN = r'>File not found...<'
-
- LINK_PATTERN = r'<a href="(.+?)" class="item_href">'
-
- PASSWORD_PATTERN = r'<div id="pwd_protected">'
-
-
- def checkErrors(self):
- m = re.search(self.PASSWORD_PATTERN, self.html)
- if m:
- self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
-
- if re.search(self.PASSWORD_PATTERN, self.html):
- self.retry(reason="Wrong password")
-
-
-getInfo = create_getInfo(CloudzillaToFolder)
diff --git a/module/plugins/crypter/CrockoComFolder.py b/module/plugins/crypter/CrockoComFolder.py
deleted file mode 100644
index f56cc449a..000000000
--- a/module/plugins/crypter/CrockoComFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class CrockoComFolder(SimpleCrypter):
- __name__ = "CrockoComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?crocko\.com/f/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Crocko.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<td class="last"><a href="(.+?)">download</a>'
-
-
-getInfo = create_getInfo(CrockoComFolder)
diff --git a/module/plugins/crypter/CryptItCom.py b/module/plugins/crypter/CryptItCom.py
deleted file mode 100644
index 2cf4e9f62..000000000
--- a/module/plugins/crypter/CryptItCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class CryptItCom(DeadCrypter):
- __name__ = "CryptItCom"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?crypt-it\.com/(s|e|d|c)/\w+'
- __config__ = []
-
- __description__ = """Crypt-it.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de")]
-
-
-getInfo = create_getInfo(CryptItCom)
diff --git a/module/plugins/crypter/CzshareComFolder.py b/module/plugins/crypter/CzshareComFolder.py
deleted file mode 100644
index c317b1b49..000000000
--- a/module/plugins/crypter/CzshareComFolder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class CzshareComFolder(Crypter):
- __name__ = "CzshareComFolder"
- __type__ = "crypter"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/folders/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Czshare.com folder decrypter plugin, now Sdilej.cz"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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.S)
- if m is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/module/plugins/crypter/DailymotionComFolder.py b/module/plugins/crypter/DailymotionComFolder.py
deleted file mode 100644
index 01caa0bb8..000000000
--- a/module/plugins/crypter/DailymotionComFolder.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class DailymotionComFolder(Crypter):
- __name__ = "DailymotionComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?dailymotion\.com/((playlists/)?(?P<TYPE>playlist|user)/)?(?P<ID>[\w^_]+)(?(TYPE)|#)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Dailymotion.com channel & playlist decrypter"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def api_response(self, ref, req=None):
- url = urlparse.urljoin("https://api.dailymotion.com/", ref)
- html = self.load(url, get=req)
- return json_loads(html)
-
-
- 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 67f5e788f..000000000
--- a/module/plugins/crypter/DataHuFolder.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DataHuFolder(SimpleCrypter):
- __name__ = "DataHuFolder"
- __type__ = "crypter"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?data\.hu/dir/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Data.hu folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("crash", None),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a href=\'(http://data\.hu/get/.+)\' target=\'_blank\'>\1</a>'
- NAME_PATTERN = ur'<title>(?P<N>.+) Let\xf6lt\xe9se</title>'
-
-
- def prepare(self):
- super(DataHuFolder, self).prepare()
-
- if u'K\xe9rlek add meg a jelsz\xf3t' in self.html: # Password protected
- password = self.getPassword()
- if not password:
- self.fail(_("Password required"))
-
- self.logDebug("The folder is password protected', 'Using password: " + password)
-
- self.html = self.load(self.pyfile.url, post={'mappa_pass': password}, decode=True)
-
- if u'Hib\xe1s jelsz\xf3' in self.html: # Wrong password
- self.fail(_("Wrong password"))
-
-
-getInfo = create_getInfo(DataHuFolder)
diff --git a/module/plugins/crypter/DdlstorageComFolder.py b/module/plugins/crypter/DdlstorageComFolder.py
deleted file mode 100644
index e02e77fda..000000000
--- a/module/plugins/crypter/DdlstorageComFolder.py
+++ /dev/null
@@ -1,20 +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+'
- __config__ = []
-
- __description__ = """DDLStorage.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(DdlstorageComFolder)
diff --git a/module/plugins/crypter/DepositfilesComFolder.py b/module/plugins/crypter/DepositfilesComFolder.py
deleted file mode 100644
index 46ec265c3..000000000
--- a/module/plugins/crypter/DepositfilesComFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DepositfilesComFolder(SimpleCrypter):
- __name__ = "DepositfilesComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?depositfiles\.com/folders/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Depositfiles.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<div class="progressName".*?>\s*<a href="(.+?)" title=".+?" target="_blank">'
-
-
-getInfo = create_getInfo(DepositfilesComFolder)
diff --git a/module/plugins/crypter/Dereferer.py b/module/plugins/crypter/Dereferer.py
deleted file mode 100644
index 8427c1540..000000000
--- a/module/plugins/crypter/Dereferer.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleDereferer import SimpleDereferer
-
-
-class Dereferer(SimpleDereferer):
- __name__ = "Dereferer"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'https?://([^/]+)/.*?(?P<LINK>(ht|f)tps?(://|%3A%2F%2F).+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Crypter for dereferers"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/module/plugins/crypter/DevhostStFolder.py b/module/plugins/crypter/DevhostStFolder.py
deleted file mode 100644
index 4d15e2058..000000000
--- a/module/plugins/crypter/DevhostStFolder.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://d-h.st/users/shine/?fld_id=37263#files
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DevhostStFolder(SimpleCrypter):
- __name__ = "DevhostStFolder"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?d-h\.st/users/(?P<USER>\w+)(/\?fld_id=(?P<ID>\d+))?'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """d-h.st folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'(?:/> |;">)<a href="(.+?)"(?!>Back to \w+<)'
- OFFLINE_PATTERN = r'"/cHP">test\.png<'
-
-
- def checkNameSize(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("File info (BEFORE): %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("File info (AFTER): %s" % self.info)
-
- try:
- if self.info['pattern']['ID'] == "0":
- raise
-
- p = r'href="(.+?)">Back to \w+<'
- m = re.search(p, self.html)
- html = self.load(urlparse.urljoin("http://d-h.st", m.group(1)),
- cookies=False)
-
- p = '\?fld_id=%s.*?">(.+?)<' % self.info['pattern']['ID']
- m = re.search(p, html)
- self.pyfile.name = m.group(1)
-
- except Exception, e:
- self.logDebug(e)
- self.pyfile.name = self.info['pattern']['USER']
-
- try:
- folder = self.info['folder'] = self.pyfile.name
-
- except Exception:
- pass
-
- self.logDebug("File name: %s" % self.pyfile.name,
- "File folder: %s" % self.pyfile.name)
-
-
-getInfo = create_getInfo(DevhostStFolder)
diff --git a/module/plugins/crypter/DlProtectCom.py b/module/plugins/crypter/DlProtectCom.py
deleted file mode 100644
index a9f39c6f3..000000000
--- a/module/plugins/crypter/DlProtectCom.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from base64 import urlsafe_b64encode
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class DlProtectCom(SimpleCrypter):
- __name__ = "DlProtectCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?dl-protect\.com/((en|fr)/)?\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Dl-protect.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- COOKIES = [("dl-protect.com", "l", "en")]
-
- 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"https?://(?:www\.)?dl-protect\.com/.+", self.req.http.lastEffectiveURL):
- return [self.req.http.lastEffectiveURL]
-
- post_req = {'key' : re.search(r'name="key" value="(.+?)"', self.html).group(1),
- 'submitform': ""}
-
- if "Please click on continue to see the content" in self.html:
- post_req['submitform'] = "Continue"
- self.wait(2)
-
- else:
- mstime = int(round(time.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:]))
-
- return re.findall(r'<a href="([^/].+?)" target="_blank">', self.html)
-
-
-getInfo = create_getInfo(DlProtectCom)
diff --git a/module/plugins/crypter/DontKnowMe.py b/module/plugins/crypter/DontKnowMe.py
deleted file mode 100644
index e56751972..000000000
--- a/module/plugins/crypter/DontKnowMe.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleDereferer import SimpleDereferer
-
-
-class DontKnowMe(SimpleDereferer):
- __name__ = "DontKnowMe"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?dontknow\.me/at/\?(?P<LINK>.+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """DontKnow.me decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("selaux", "")]
diff --git a/module/plugins/crypter/DuckCryptInfo.py b/module/plugins/crypter/DuckCryptInfo.py
deleted file mode 100644
index cc108d101..000000000
--- a/module/plugins/crypter/DuckCryptInfo.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from 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*)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """DuckCrypt.info decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
- TIMER_PATTERN = r'<span id="timer">(.*)</span>'
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
-
- 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):
- html = self.load("http://duckcrypt.info/ajax/auth.php?hash=" + str(m.group(2)))
- m = re.match(self.__pattern__, html)
- self.logDebug("Redirectet to " + str(m.group(0)))
- html = self.load(str(m.group(0)))
- soup = BeautifulSoup(html)
- cryptlinks = soup.findAll("div", attrs={"class": "folderbox"})
- self.logDebug("Redirectet to " + str(cryptlinks))
- if not cryptlinks:
- self.error(_("No link found"))
- for clink in cryptlinks:
- if clink.find("a"):
- self.handleLink(clink.find("a")['href'])
-
-
- def handleLink(self, url):
- html = self.load(url)
- soup = BeautifulSoup(html)
- self.urls = [soup.find("iframe")['src']]
- if not self.urls:
- self.logInfo(_("No link found"))
diff --git a/module/plugins/crypter/DuploadOrgFolder.py b/module/plugins/crypter/DuploadOrgFolder.py
deleted file mode 100644
index 066fbe3d7..000000000
--- a/module/plugins/crypter/DuploadOrgFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class DuploadOrgFolder(DeadCrypter):
- __name__ = "DuploadOrgFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?dupload\.org/folder/\d+'
- __config__ = []
-
- __description__ = """Dupload.org folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(DuploadOrgFolder)
diff --git a/module/plugins/crypter/EasybytezComFolder.py b/module/plugins/crypter/EasybytezComFolder.py
deleted file mode 100644
index fdd3d4ff4..000000000
--- a/module/plugins/crypter/EasybytezComFolder.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSCrypter import XFSCrypter, create_getInfo
-
-
-class EasybytezComFolder(XFSCrypter):
- __name__ = "EasybytezComFolder"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?easybytez\.com/users/\d+/\d+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Easybytez.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LOGIN_ACCOUNT = True
-
-
-getInfo = create_getInfo(EasybytezComFolder)
diff --git a/module/plugins/crypter/EmbeduploadCom.py b/module/plugins/crypter/EmbeduploadCom.py
deleted file mode 100644
index 28633f634..000000000
--- a/module/plugins/crypter/EmbeduploadCom.py
+++ /dev/null
@@ -1,60 +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__ = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True ),
- ("preferedHoster" , "str" , "Prefered hoster list (bar-separated)", "embedupload"),
- ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
-
- __description__ = """EmbedUpload.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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)
-
- self.logDebug("PF: %s" % 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)
-
- self.logDebug("IG: %s" % ignored_set)
-
- tmp_links.extend(x[1] for x in m if x[0] not in ignored_set)
- self.urls = self.getLocation(tmp_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 a3c7ee74c..000000000
--- a/module/plugins/crypter/FilebeerInfoFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class FilebeerInfoFolder(DeadCrypter):
- __name__ = "FilebeerInfoFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?filebeer\.info/\d*~f\w+'
- __config__ = []
-
- __description__ = """Filebeer.info folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(FilebeerInfoFolder)
diff --git a/module/plugins/crypter/FilecloudIoFolder.py b/module/plugins/crypter/FilecloudIoFolder.py
deleted file mode 100644
index f900f4910..000000000
--- a/module/plugins/crypter/FilecloudIoFolder.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilecloudIoFolder(SimpleCrypter):
- __name__ = "FilecloudIoFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?(filecloud\.io|ifile\.it)/_\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filecloud.io folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'href="(http://filecloud\.io/\w+)" title'
- NAME_PATTERN = r'>(?P<N>.+?) - filecloud\.io<'
-
-
-getInfo = create_getInfo(FilecloudIoFolder)
diff --git a/module/plugins/crypter/FilecryptCc.py b/module/plugins/crypter/FilecryptCc.py
deleted file mode 100644
index a1a94b6f6..000000000
--- a/module/plugins/crypter/FilecryptCc.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://filecrypt.cc/Container/64E039F859.html
-
-import binascii
-import re
-import urlparse
-
-from Crypto.Cipher import AES
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class FilecryptCc(Crypter):
- __name__ = "FilecryptCc"
- __type__ = "crypter"
- __version__ = "0.14"
-
- __pattern__ = r'https?://(?:www\.)?filecrypt\.cc/Container/\w+'
-
- __description__ = """Filecrypt.cc decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "")]
-
-
- # URL_REPLACEMENTS = [(r'.html$', ""), (r'$', ".html")] #@TODO: Extend SimpleCrypter
-
- DLC_LINK_PATTERN = r'<button class="dlcdownload" type="button" title="Download \*.dlc" onclick="DownloadDLC\(\'(.+)\'\);"><i></i><span>dlc<'
- WEBLINK_PATTERN = r"openLink.?'([\w_-]*)',"
-
- CAPTCHA_PATTERN = r'<img id="nc" src="(.+?)"'
- CIRCLE_CAPTCHA_PATTERN = r'<input type="image" src="(.+?)"'
-
- MIRROR_PAGE_PATTERN = r'"[\w]*" href="(https?://(?:www\.)?filecrypt.cc/Container/\w+\.html\?mirror=\d+)">'
-
-
- def setup(self):
- self.links = []
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url)
- self.base_url = self.pyfile.url.split("Container")[0]
-
- if "content notfound" in self.html: #@NOTE: "content notfound" is NOT a typo
- self.offline()
-
- self.handlePasswordProtection()
- self.handleCaptcha()
- self.handleMirrorPages()
-
- for handle in (self.handleCNL, self.handleWeblinks, self.handleDlcContainer):
- handle()
- if self.links:
- self.packages = [(pyfile.package().name, self.links, pyfile.package().name)]
- return
-
-
- def handleMirrorPages(self):
- if "mirror=" not in self.siteWithLinks:
- return
-
- mirror = re.findall(self.MIRROR_PAGE_PATTERN, self.siteWithLinks)
-
- self.logInfo(_("Found %d mirrors") % len(mirror))
-
- for i in mirror[1:]:
- self.siteWithLinks = self.siteWithLinks + self.load(i).decode("utf-8", "replace")
-
-
- def handlePasswordProtection(self):
- if '<input type="text" name="password"' not in self.html:
- return
-
- self.logInfo(_("Folder is password protected"))
-
- password = self.getPassword()
-
- if not password:
- self.fail(_("Please enter the password in package section and try again"))
-
- self.html = self.load(self.pyfile.url, post={"password": password})
-
-
- def handleCaptcha(self):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- m2 = re.search(self.CIRCLE_CAPTCHA_PATTERN, self.html)
-
- if m: #: normal captcha
- self.logDebug("Captcha-URL: %s" % m.group(1))
-
- captcha_code = self.decryptCaptcha(urlparse.urljoin(self.base_url, m.group(1)),
- forceUser=True,
- imgtype="gif")
-
- self.siteWithLinks = self.load(self.pyfile.url,
- post={'recaptcha_response_field': captcha_code},
- decode=True)
- elif m2: #: circle captcha
- self.logDebug("Captcha-URL: %s" % m2.group(1))
-
- captcha_code = self.decryptCaptcha('%s%s?c=abc' %(self.base_url, m2.group(1)),
- result_type='positional')
-
- self.siteWithLinks = self.load(self.pyfile.url,
- post={'button.x': captcha_code[0], 'button.y': captcha_code[1]},
- decode=True)
-
- else:
- recaptcha = ReCaptcha(self)
- captcha_key = recaptcha.detect_key()
-
- if captcha_key:
- response, challenge = recaptcha.challenge(captcha_key)
- self.siteWithLinks = self.load(self.pyfile.url,
- post={'g-recaptcha-response': response},
- decode=True)
- else:
- self.logInfo(_("No captcha found"))
- self.siteWithLinks = self.html
-
- if "recaptcha_image" in self.siteWithLinks or "data-sitekey" in self.siteWithLinks:
- self.invalidCaptcha()
- self.retry()
-
-
- def handleDlcContainer(self):
- dlc = re.findall(self.DLC_LINK_PATTERN, self.siteWithLinks)
-
- if not dlc:
- return
-
- for i in dlc:
- self.links.append("%s/DLC/%s.dlc" % (self.base_url, i))
-
-
- def handleWeblinks(self):
- try:
- weblinks = re.findall(self.WEBLINK_PATTERN, self.siteWithLinks)
-
- for link in weblinks:
- res = self.load("%s/Link/%s.html" % (self.base_url, link))
- link2 = re.search('<iframe noresize src="(.*)"></iframe>', res)
- res2 = self.load(link2.group(1), just_header=True)
- self.links.append(res2['location'])
-
- except Exception, e:
- self.logDebug("Error decrypting weblinks: %s" % e)
-
-
- def handleCNL(self):
- try:
- vjk = re.findall('<input type="hidden" name="jk" value="function f\(\){ return \'(.*)\';}">', self.siteWithLinks)
- vcrypted = re.findall('<input type="hidden" name="crypted" value="(.*)">', self.siteWithLinks)
-
- for i in xrange(len(vcrypted)):
- self.links.extend(self._getLinks(vcrypted[i], vjk[i]))
-
- except Exception, e:
- self.logDebug("Error decrypting CNL: %s" % e)
-
-
- def _getLinks(self, crypted, jk):
- # Get key
- key = binascii.unhexlify(str(jk))
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- return links
diff --git a/module/plugins/crypter/FilefactoryComFolder.py b/module/plugins/crypter/FilefactoryComFolder.py
deleted file mode 100644
index c0cd028cc..000000000
--- a/module/plugins/crypter/FilefactoryComFolder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilefactoryComFolder(SimpleCrypter):
- __name__ = "FilefactoryComFolder"
- __type__ = "crypter"
- __version__ = "0.32"
-
- __pattern__ = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filefactory.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
-
- LINK_PATTERN = r'<td>\s*<a href="(.+?)"'
- NAME_PATTERN = r'<h1>Files in <span>(?P<N>.+?)<'
- PAGES_PATTERN = r'data-paginator-totalPages="(\d+)'
-
-
- def loadPage(self, page_n):
- return self.load(self.pyfile.url, get={'page': page_n, 'show': 100})
-
-
-getInfo = create_getInfo(FilefactoryComFolder)
diff --git a/module/plugins/crypter/FilerNetFolder.py b/module/plugins/crypter/FilerNetFolder.py
deleted file mode 100644
index e2b44e868..000000000
--- a/module/plugins/crypter/FilerNetFolder.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilerNetFolder(SimpleCrypter):
- __name__ = "FilerNetFolder"
- __type__ = "crypter"
- __version__ = "0.42"
-
- __pattern__ = r'https?://filer\.net/folder/\w{16}'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filer.net decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("nath_schwarz", "nathan.notwhite@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'href="(/get/\w{16})">(?!<)'
-
- NAME_PATTERN = r'<h3>(?P<N>.+?) - <small'
- OFFLINE_PATTERN = r'Nicht gefunden'
-
-
-getInfo = create_getInfo(FilerNetFolder)
diff --git a/module/plugins/crypter/FileserveComFolder.py b/module/plugins/crypter/FileserveComFolder.py
deleted file mode 100644
index 1363e2d45..000000000
--- a/module/plugins/crypter/FileserveComFolder.py
+++ /dev/null
@@ -1,38 +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+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """FileServe.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fionnc", "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.S)
- if folder is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- 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)]
diff --git a/module/plugins/crypter/FilesonicComFolder.py b/module/plugins/crypter/FilesonicComFolder.py
deleted file mode 100644
index 8fc28d592..000000000
--- a/module/plugins/crypter/FilesonicComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class FilesonicComFolder(DeadCrypter):
- __name__ = "FilesonicComFolder"
- __type__ = "crypter"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?filesonic\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Filesonic.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(FilesonicComFolder)
diff --git a/module/plugins/crypter/FilestubeCom.py b/module/plugins/crypter/FilestubeCom.py
deleted file mode 100644
index 133f4a53f..000000000
--- a/module/plugins/crypter/FilestubeCom.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FilestubeCom(SimpleCrypter):
- __name__ = "FilestubeCom"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?filestube\.(?:com|to)/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filestube.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a class=\"file-link-main(?: noref)?\" [^>]* href=\"(http://[^\"]+)'
- NAME_PATTERN = r'<h1\s*> (?P<N>.+) download\s*</h1>'
-
-
-getInfo = create_getInfo(FilestubeCom)
diff --git a/module/plugins/crypter/FiletramCom.py b/module/plugins/crypter/FiletramCom.py
deleted file mode 100644
index b012d35bc..000000000
--- a/module/plugins/crypter/FiletramCom.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FiletramCom(SimpleCrypter):
- __name__ = "FiletramCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?filetram\.com/[^/]+/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Filetram.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'\s+(http://.+)'
- NAME_PATTERN = r'<title>(?P<N>.+?) - Free Download'
-
-
-getInfo = create_getInfo(FiletramCom)
diff --git a/module/plugins/crypter/FiredriveComFolder.py b/module/plugins/crypter/FiredriveComFolder.py
deleted file mode 100644
index 7d3a357fd..000000000
--- a/module/plugins/crypter/FiredriveComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class FiredriveComFolder(DeadCrypter):
- __name__ = "FiredriveComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/share/.+'
- __config__ = []
-
- __description__ = """Firedrive.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(FiredriveComFolder)
diff --git a/module/plugins/crypter/FourChanOrg.py b/module/plugins/crypter/FourChanOrg.py
deleted file mode 100644
index c90c84b6f..000000000
--- a/module/plugins/crypter/FourChanOrg.py
+++ /dev/null
@@ -1,27 +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.31"
-
- __pattern__ = r'http://(?:www\.)?boards\.4chan\.org/\w+/res/(\d+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """4chan.org folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
- 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 173660668..000000000
--- a/module/plugins/crypter/FreakhareComFolder.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FreakhareComFolder(SimpleCrypter):
- __name__ = "FreakhareComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?freakshare\.com/folder/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Freakhare.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<a href="(http://freakshare\.com/files/.+?)" target="_blank">'
- NAME_PATTERN = r'Folder:</b> (?P<N>.+)'
- PAGES_PATTERN = r'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)
-
-
-getInfo = create_getInfo(FreakhareComFolder)
diff --git a/module/plugins/crypter/FreetexthostCom.py b/module/plugins/crypter/FreetexthostCom.py
deleted file mode 100644
index cf6fbb8a4..000000000
--- a/module/plugins/crypter/FreetexthostCom.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FreetexthostCom(SimpleCrypter):
- __name__ = "FreetexthostCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?freetexthost\.com/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Freetexthost.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def getLinks(self):
- m = re.search(r'<div id="contentsinner">\s*(.+)<div class="viewcount">', self.html, re.S)
- if m is None:
- self.error(_("Unable to extract links"))
- links = m.group(1)
- return links.strip().split("<br />\r\n")
-
-
-getInfo = create_getInfo(FreetexthostCom)
diff --git a/module/plugins/crypter/FshareVnFolder.py b/module/plugins/crypter/FshareVnFolder.py
deleted file mode 100644
index bbee53337..000000000
--- a/module/plugins/crypter/FshareVnFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class FshareVnFolder(SimpleCrypter):
- __name__ = "FshareVnFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?fshare\.vn/folder/.+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Fshare.vn folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<li class="w_80pc"><a href="(.+?)" target="_blank">'
-
-
-getInfo = create_getInfo(FshareVnFolder)
diff --git a/module/plugins/crypter/Go4UpCom.py b/module/plugins/crypter/Go4UpCom.py
deleted file mode 100644
index 22f31f6f6..000000000
--- a/module/plugins/crypter/Go4UpCom.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class Go4UpCom(SimpleCrypter):
- __name__ = "Go4UpCom"
- __type__ = "crypter"
- __version__ = "0.12"
-
- __pattern__ = r'http://go4up\.com/(dl/\w{12}|rd/\w{12}/\d+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Go4Up.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("rlindner81", "rlindner81@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'(http://go4up\.com/rd/.+?)<'
-
- NAME_PATTERN = r'<title>Download (.+?)<'
-
- OFFLINE_PATTERN = r'>\s*(404 Page Not Found|File not Found|Mirror does not exist)'
-
-
- def getLinks(self):
- links = []
-
- m = re.search(r'(/download/gethosts/.+?)"', self.html)
- if m:
- self.html = self.load(urlparse.urljoin("http://go4up.com/", m.group(1)))
- pages = [self.load(url) for url in re.findall(self.LINK_PATTERN, self.html)]
- else:
- pages = [self.html]
-
- for html in pages:
- try:
- links.append(re.search(r'<b><a href="(.+?)"', html).group(1))
- except Exception:
- continue
-
- return links
-
-
-getInfo = create_getInfo(Go4UpCom)
diff --git a/module/plugins/crypter/GooGl.py b/module/plugins/crypter/GooGl.py
deleted file mode 100644
index b5f0c0447..000000000
--- a/module/plugins/crypter/GooGl.py
+++ /dev/null
@@ -1,32 +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+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Goo.gl decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "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 ccc3ab664..000000000
--- a/module/plugins/crypter/HoerbuchIn.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup
-
-from module.plugins.Crypter import Crypter
-
-
-class HoerbuchIn(Crypter):
- __name__ = "HoerbuchIn"
- __type__ = "crypter"
- __version__ = "0.60"
-
- __pattern__ = r'http://(?:www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out\.php\?.+|protection/folder_\d+\.html)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Hoerbuch.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("mkaay", "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):
- html = self.load(pyfile.url)
- soup = BeautifulSoup(html, 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
- html = self.load(url, post={"viewed": "adpg"})
-
- links = []
- pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"")
- for hoster, lid in pattern.findall(html):
- 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/HotfileComFolder.py b/module/plugins/crypter/HotfileComFolder.py
deleted file mode 100644
index 4f40587ad..000000000
--- a/module/plugins/crypter/HotfileComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class HotfileComFolder(DeadCrypter):
- __name__ = "HotfileComFolder"
- __type__ = "crypter"
- __version__ = "0.30"
-
- __pattern__ = r'https?://(?:www\.)?hotfile\.com/list/\w+/\w+'
- __config__ = []
-
- __description__ = """Hotfile.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
-getInfo = create_getInfo(HotfileComFolder)
diff --git a/module/plugins/crypter/ILoadTo.py b/module/plugins/crypter/ILoadTo.py
deleted file mode 100644
index f3415706d..000000000
--- a/module/plugins/crypter/ILoadTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class ILoadTo(DeadCrypter):
- __name__ = "ILoadTo"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?iload\.to/go/\d+-[\w.-]+/'
- __config__ = []
-
- __description__ = """Iload.to decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("hzpz", None)]
-
-
-getInfo = create_getInfo(ILoadTo)
diff --git a/module/plugins/crypter/ImgurComAlbum.py b/module/plugins/crypter/ImgurComAlbum.py
deleted file mode 100644
index 9ef7797af..000000000
--- a/module/plugins/crypter/ImgurComAlbum.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-from module.utils import uniqify
-
-
-class ImgurComAlbum(SimpleCrypter):
- __name__ = "ImgurComAlbum"
- __type__ = "crypter"
- __version__ = "0.51"
-
- __pattern__ = r'https?://(?:www\.|m\.)?imgur\.com/(a|gallery|)/?\w{5,7}'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Imgur.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("nath_schwarz", "nathan.notwhite@gmail.com")]
-
-
- NAME_PATTERN = r'(?P<N>.+?) - 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)))
-
-
-getInfo = create_getInfo(ImgurComAlbum)
diff --git a/module/plugins/crypter/LetitbitNetFolder.py b/module/plugins/crypter/LetitbitNetFolder.py
deleted file mode 100644
index b60d754a7..000000000
--- a/module/plugins/crypter/LetitbitNetFolder.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class LetitbitNetFolder(Crypter):
- __name__ = "LetitbitNetFolder"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?letitbit\.net/folder/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Letitbit.net folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("DHMH", "webmaster@pcProfil.de"),
- ("z00nx", "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.S)
- if folder is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- self.urls.extend(re.findall(self.LINK_PATTERN, folder.group(0)))
diff --git a/module/plugins/crypter/LinkCryptWs.py b/module/plugins/crypter/LinkCryptWs.py
deleted file mode 100644
index d3e75aad4..000000000
--- a/module/plugins/crypter/LinkCryptWs.py
+++ /dev/null
@@ -1,322 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import binascii
-import re
-
-import pycurl
-
-from Crypto.Cipher import AES
-
-from module.plugins.Crypter import Crypter
-from module.utils import html_unescape
-
-
-class LinkCryptWs(Crypter):
- __name__ = "LinkCryptWs"
- __type__ = "crypter"
- __version__ = "0.08"
-
- __pattern__ = r'http://(?:www\.)?linkcrypt\.ws/(dir|container)/(?P<ID>\w+)'
-
- __description__ = """LinkCrypt.ws decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("kagenoshin", "kagenoshin[AT]gmx[DOT]ch"),
- ("glukgluk", None),
- ("Gummibaer", None)]
-
-
- CRYPTED_KEY = "crypted"
- JK_KEY = "jk"
-
-
- def setup(self):
- self.captcha = False
- self.links = []
- self.sources = ['cnl', 'web', 'dlc', 'rsdf', 'ccf']
-
-
- def prepare(self):
- # Init
- self.fileid = re.match(self.__pattern__, self.pyfile.url).group('ID')
-
- self.req.cj.setCookie("linkcrypt.ws", "language", "en")
-
- # Request package
- self.req.http.c.setopt(pycurl.USERAGENT, "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko") #: better chance to not get those key-captchas
- self.html = self.load(self.pyfile.url)
-
-
- def decrypt(self, pyfile):
- if not self.js:
- self.fail(_("Missing JS Engine"))
-
- self.prepare()
-
- if not self.isOnline():
- self.offline()
-
- if self.isKeyCaptchaProtected():
- self.retry(8, 15, _("Can't handle Key-Captcha"))
-
- if self.isCaptchaProtected():
- self.captcha = True
- self.unlockCaptchaProtection()
- self.handleCaptchaErrors()
-
- # Check for protection
- if self.isPasswordProtected():
- self.unlockPasswordProtection()
- self.handleErrors()
-
- # get unrar password
- self.getunrarpw()
-
- # Get package name and folder
- package_name, folder_name = self.getPackageInfo()
-
- #get the container definitions from script section
- self.get_container_html()
-
- # Extract package links
- for type in self.sources:
- links = self.handleLinkSource(type)
-
- if links:
- self.links.extend(links)
- break
-
- if self.links:
- self.packages = [(package_name, self.links, folder_name)]
-
-
- def isOnline(self):
- if "<title>Linkcrypt.ws // Error 404</title>" in self.html:
- self.logDebug("folder doesen't exist anymore")
- return False
- else:
- return True
-
-
- def isPasswordProtected(self):
- if "Authorizing" in self.html:
- self.logDebug("Links are password protected")
- return True
- else:
- return False
-
-
- def isCaptchaProtected(self):
- if 'id="captcha">' in self.html:
- self.logDebug("Links are captcha protected")
- return True
- else:
- return False
-
-
- def isKeyCaptchaProtected(self):
- if re.search(r'>If the folder does not open after klick on <', self.html, re.I):
- return True
- else:
- return False
-
-
- def unlockPasswordProtection(self):
- password = self.getPassword()
-
- if password:
- self.logDebug("Submitting password [%s] for protected links" % password)
- self.html = self.load(self.pyfile.url, post={"password": password, 'x': "0", 'y': "0"})
- else:
- self.fail(_("Folder is password protected"))
-
-
- def unlockCaptchaProtection(self):
- captcha_url = re.search(r'<form.*?id\s*?=\s*?"captcha"[^>]*?>.*?<\s*?input.*?src="(.+?)"', self.html, re.I | re.S).group(1)
- captcha_code = self.decryptCaptcha(captcha_url, forceUser=True, imgtype="gif", result_type='positional')
-
- self.html = self.load(self.pyfile.url, post={"x": captcha_code[0], "y": captcha_code[1]})
-
-
- 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 getunrarpw(self):
- sitein = self.html
- indexi = sitein.find("|source|") + 8
- indexe = sitein.find("|",indexi)
-
- unrarpw = sitein[indexi:indexe]
-
- if not (unrarpw == "Password" or "Dateipasswort") :
- self.logDebug("File password set to: [%s]"% unrarpw)
- self.pyfile.package().password = unrarpw
-
-
- def handleErrors(self):
- if self.isPasswordProtected():
- self.fail(_("Incorrect password"))
-
-
- def handleCaptchaErrors(self):
- if self.captcha:
- if "Your choice was wrong!" in self.html:
- self.invalidCaptcha()
- self.retry()
- else:
- self.correctCaptcha()
-
-
- def handleLinkSource(self, type):
- if type == 'cnl':
- return self.handleCNL2()
-
- elif type == 'web':
- return self.handleWebLinks()
-
- elif type in ('rsdf', 'ccf', 'dlc'):
- return self.handleContainer(type)
-
- else:
- self.fail(_("Unknown source type: %s") % type) #@TODO: Replace with self.error in 0.4.10
-
-
- def handleWebLinks(self):
- self.logDebug("Search for Web links ")
-
- package_links = []
- pattern = r'<form action="http://linkcrypt.ws/out.html"[^>]*?>.*?<input[^>]*?value="(.+?)"[^>]*?name="file"'
- ids = re.findall(pattern, self.html, re.I | re.S)
-
- self.logDebug("Decrypting %d Web links" % len(ids))
-
- for idx, weblink_id in enumerate(ids):
- try:
- self.logDebug("Decrypting Web link %d, %s" % (idx + 1, weblink_id))
-
- res = self.load("http://linkcrypt.ws/out.html", post = {'file':weblink_id})
-
- indexs = res.find("window.location =") + 19
- indexe = res.find('"', indexs)
-
- link2 = res[indexs:indexe]
-
- self.logDebug(link2)
-
- link2 = html_unescape(link2)
- package_links.append(link2)
-
- except Exception, detail:
- self.logDebug("Error decrypting Web link %s, %s" % (weblink_id, detail))
-
- return package_links
-
-
- def get_container_html(self):
- self.container_html = []
-
- script = re.search(r'<div.*?id="ad_cont".*?<script.*?javascrip[^>]*?>(.*?)</script', self.html, re.I | re.S)
-
- if script:
- container_html_text = script.group(1)
- container_html_text.strip()
- self.container_html = container_html_text.splitlines()
-
-
- def handle_javascript(self, line):
- return self.js.eval(line.replace('{}))',"{}).replace('document.open();document.write','').replace(';document.close();',''))"))
-
-
- def handleContainer(self, type):
- package_links = []
- type = type.lower()
-
- self.logDebug('Search 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") % type) #@TODO: Replace with self.error in 0.4.10
-
- for line in self.container_html:
- if type in line:
- jseval = self.handle_javascript(line)
- clink = re.search(r'href=["\'](["\']+)', jseval, re.I)
-
- if not clink:
- continue
-
- self.logDebug("clink avaible")
-
- package_name, folder_name = self.getPackageInfo()
- self.logDebug("Added package with name %s.%s and container link %s" %( package_name, type, clink.group(1)))
- self.core.api.uploadContainer( "%s.%s" %(package_name, type), self.load(clink.group(1)))
- return "Found it"
-
- return package_links
-
-
- def handleCNL2(self):
- self.logDebug("Search for CNL links")
-
- package_links = []
- cnl_line = None
-
- for line in self.container_html:
- if "cnl" in line:
- cnl_line = line
- break
-
- if cnl_line:
- self.logDebug("cnl_line gefunden")
-
- try:
- cnl_section = self.handle_javascript(cnl_line)
- (vcrypted, vjk) = self._getCipherParams(cnl_section)
- for (crypted, jk) in zip(vcrypted, vjk):
- package_links.extend(self._getLinks(crypted, jk))
- except Exception:
- self.logError(_("Unable to decrypt CNL links (JS Error) try to get over links"))
- return self.handleWebLinks()
-
- return package_links
-
-
- def _getCipherParams(self, cnl_section):
- # Get jk
- jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.JK_KEY
- vjk = re.findall(jk_re, cnl_section)
-
- # Get crypted
- crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.CRYPTED_KEY
- vcrypted = re.findall(crypted_re, cnl_section)
-
- # 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)
- key = binascii.unhexlify(jreturn)
-
- self.logDebug("JsEngine returns value [%s]" % jreturn)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # Log and return
- self.logDebug("Package has %d links" % len(links))
-
- return links
diff --git a/module/plugins/crypter/LinkSaveIn.py b/module/plugins/crypter/LinkSaveIn.py
deleted file mode 100644
index 040305bd9..000000000
--- a/module/plugins/crypter/LinkSaveIn.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleDereferer import SimpleDereferer
-
-
-class LinkSaveIn(SimpleDereferer):
- __name__ = "LinkSaveIn"
- __type__ = "crypter"
- __version__ = "2.03"
-
- __pattern__ = r'https?://(?:www\.)?linksave\.in/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """LinkSave.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- COOKIES = [("linksave.in", "Linksave_Language", "english")]
-
- OFFLINE_PATTERN = r'>(Error )?404 -'
diff --git a/module/plugins/crypter/LinkdecrypterCom.py b/module/plugins/crypter/LinkdecrypterCom.py
deleted file mode 100644
index 0704214d0..000000000
--- a/module/plugins/crypter/LinkdecrypterCom.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class LinkdecrypterCom(Crypter):
- __name__ = "LinkdecrypterCom"
- __type__ = "crypter"
- __version__ = "0.29"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Linkdecrypter.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("flowlee", None)]
-
-
- 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 setup(self):
- self.password = self.getPassword()
- self.req.setOption("timeout", 300)
-
-
- def decrypt(self, pyfile):
- retries = 5
-
- post_dict = {"link_cache": "on", "pro_links": pyfile.url, "modo_links": "text"}
- self.html = self.load('http://linkdecrypter.com/', post=post_dict, decode=True)
-
- while retries:
- m = re.search(self.TEXTAREA_PATTERN, self.html, re.S)
- if m:
- self.urls = [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.password:
- self.logInfo(_("Password protected link"))
- self.html = self.load('http://linkdecrypter.com/', post={'password': self.password}, decode=True)
- else:
- self.fail(_("Missing password"))
-
- else:
- retries -= 1
- self.html = self.load('http://linkdecrypter.com/', decode=True)
diff --git a/module/plugins/crypter/LixIn.py b/module/plugins/crypter/LixIn.py
deleted file mode 100644
index 66a0cf339..000000000
--- a/module/plugins/crypter/LixIn.py
+++ /dev/null
@@ -1,62 +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>.+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Lix.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
- CAPTCHA_PATTERN = r'<img src="(captcha_img\.php\?.*?)"'
- SUBMIT_PATTERN = r'value=\'continue.*?\''
- LINK_PATTERN = r'name="ifram" src="(.*?)"'
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
-
- m = re.match(self.__pattern__, url)
- if m is None:
- self.error(_("Unable to identify file ID"))
-
- id = m.group('ID')
- self.logDebug("File id is %s" % id)
-
- self.html = self.load(url, decode=True)
-
- m = re.search(self.SUBMIT_PATTERN, self.html)
- if m is None:
- self.error(_("Link doesn't seem valid"))
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- for _i 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(1))
- self.html = self.load(url, decode=True,
- post={"capt": captcharesult, "submit": "submit", "tiny": id})
- else:
- self.logDebug("No captcha/captcha solved")
- else:
- self.html = self.load(url, decode=True, post={"submit": "submit", "tiny": id})
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.error(_("Unable to find destination url"))
- else:
- self.urls = [m.group(1)]
- 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 3cac0fbf2..000000000
--- a/module/plugins/crypter/LofCc.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class LofCc(DeadCrypter):
- __name__ = "LofCc"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?lof\.cc/(.+)'
- __config__ = []
-
- __description__ = """Lof.cc decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(LofCc)
diff --git a/module/plugins/crypter/MBLinkInfo.py b/module/plugins/crypter/MBLinkInfo.py
deleted file mode 100644
index 82c2d9719..000000000
--- a/module/plugins/crypter/MBLinkInfo.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class MBLinkInfo(DeadCrypter):
- __name__ = "MBLinkInfo"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?mblink\.info/?\?id=(\d+)'
- __config__ = []
-
- __description__ = """MBLink.info decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Gummibaer", "Gummibaer@wiki-bierkiste.de"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(MBLinkInfo)
diff --git a/module/plugins/crypter/MediafireComFolder.py b/module/plugins/crypter/MediafireComFolder.py
deleted file mode 100644
index c1612bb6a..000000000
--- a/module/plugins/crypter/MediafireComFolder.py
+++ /dev/null
@@ -1,58 +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}($|[/#]))'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Mediafire.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- FOLDER_KEY_PATTERN = r'var afI= \'(\w+)'
- LINK_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.LINK_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",
- get={'folder_key' : folder_key,
- 'response_format': "json",
- 'version' : 1}))
- #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)
diff --git a/module/plugins/crypter/MegaCoNzFolder.py b/module/plugins/crypter/MegaCoNzFolder.py
deleted file mode 100644
index bd135ac5f..000000000
--- a/module/plugins/crypter/MegaCoNzFolder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class MegaCoNzFolder(Crypter):
- __name__ = "MegaCoNzFolder"
- __type__ = "crypter"
- __version__ = "0.04"
-
- __pattern__ = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#F!(?P<ID>[\w^_]+)!(?P<KEY>[\w,\\-]+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Mega.co.nz folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def setup(self):
- self.req.setOption("timeout", 300)
-
-
- def decrypt(self, pyfile):
- url = "https://mega.co.nz/#F!%s!%s" % re.match(self.__pattern__, pyfile.url).groups()
- self.html = self.load("http://rapidgen.org/linkfinder", post={'linklisturl': url})
- self.urls = re.findall(r'(https://mega.co.nz/#N!.+?)<', self.html)
-
- if not self.urls: #@TODO: Remove in 0.4.10
- self.fail(_("No link grabbed"))
diff --git a/module/plugins/crypter/MegaRapidCzFolder.py b/module/plugins/crypter/MegaRapidCzFolder.py
deleted file mode 100644
index fadd6dbed..000000000
--- a/module/plugins/crypter/MegaRapidCzFolder.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class MegaRapidCzFolder(SimpleCrypter):
- __name__ = "MegaRapidCzFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?(share|mega)rapid\.cz/slozka/\d+/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Share-Rapid.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<td class="soubor".*?><a href="(.+?)">'
-
-
-getInfo = create_getInfo(MegaRapidCzFolder)
diff --git a/module/plugins/crypter/MegauploadComFolder.py b/module/plugins/crypter/MegauploadComFolder.py
deleted file mode 100644
index 1af9d556d..000000000
--- a/module/plugins/crypter/MegauploadComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class MegauploadComFolder(DeadCrypter):
- __name__ = "MegauploadComFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?megaupload\.com/(\?f|xml/folderfiles\.php\?.*&?folderid)=\w+'
- __config__ = []
-
- __description__ = """Megaupload.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(MegauploadComFolder)
diff --git a/module/plugins/crypter/Movie2KTo.py b/module/plugins/crypter/Movie2KTo.py
deleted file mode 100644
index 76bf702ac..000000000
--- a/module/plugins/crypter/Movie2KTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class Movie2KTo(DeadCrypter):
- __name__ = "Movie2KTo"
- __type__ = "crypter"
- __version__ = "0.51"
-
- __pattern__ = r'http://(?:www\.)?movie2k\.to/(.+)\.html'
- __config__ = []
-
- __description__ = """Movie2k.to decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("4Christopher", "4Christopher@gmx.de")]
-
-
-getInfo = create_getInfo(Movie2KTo)
diff --git a/module/plugins/crypter/MultiUpOrg.py b/module/plugins/crypter/MultiUpOrg.py
deleted file mode 100644
index b676c3029..000000000
--- a/module/plugins/crypter/MultiUpOrg.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class MultiUpOrg(SimpleCrypter):
- __name__ = "MultiUpOrg"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?multiup\.org/(en|fr)/(?P<TYPE>project|download|miror)/\w+(/\w+)?'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """MultiUp.org crypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<title>.*(?:Project|Projet|ownload|élécharger) (?P<N>.+?) (\(|- )'
-
-
- 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 = urlparse.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)
-
-
-getInfo = create_getInfo(MultiUpOrg)
diff --git a/module/plugins/crypter/MultiloadCz.py b/module/plugins/crypter/MultiloadCz.py
deleted file mode 100644
index eea689a09..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.40"
-
- __pattern__ = r'http://(?:[^/]*\.)?multiload\.cz/(stahnout|slozka)/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True),
- ("usedHoster" , "str" , "Prefered hoster list (bar-separated)", "" ),
- ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
-
- __description__ = """Multiload.cz decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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)
diff --git a/module/plugins/crypter/MultiuploadCom.py b/module/plugins/crypter/MultiuploadCom.py
deleted file mode 100644
index 347b7e5af..000000000
--- a/module/plugins/crypter/MultiuploadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class MultiuploadCom(DeadCrypter):
- __name__ = "MultiuploadCom"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?multiupload\.(com|nl)/\w+'
- __config__ = []
-
- __description__ = """ MultiUpload.com decrypter plugin """
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(MultiuploadCom)
diff --git a/module/plugins/crypter/NCryptIn.py b/module/plugins/crypter/NCryptIn.py
deleted file mode 100644
index 8ceb9d3c8..000000000
--- a/module/plugins/crypter/NCryptIn.py
+++ /dev/null
@@ -1,310 +0,0 @@
-# -*- coding: utf-8 -*-
-
-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.34"
-
- __pattern__ = r'http://(?:www\.)?ncrypt\.in/(?P<TYPE>folder|link|frame)-([^/\?]+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """NCrypt.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("stickell", "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.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 package_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.S)
- 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.S)
- if form:
- 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.S).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)
- response, challenge = recaptcha.challenge(captcha_key)
- postData['recaptcha_challenge_field'] = challenge
- postData['recaptcha_response_field'] = response
-
- # 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.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.error(_('Unknown source type "%s"') % 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 Exception:
- 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)/(\w+)'
- 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)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # 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 ac76c059e..000000000
--- a/module/plugins/crypter/NetfolderIn.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class NetfolderIn(SimpleCrypter):
- __name__ = "NetfolderIn"
- __type__ = "crypter"
- __version__ = "0.72"
-
- __pattern__ = r'http://(?:www\.)?netfolder\.in/(folder\.php\?folder_id=)?(?P<ID>\w+)(?(1)|/\w+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """NetFolder.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("fragonib", "fragonib[AT]yahoo[DOT]es")]
-
-
- NAME_PATTERN = r'<div class="Text">Inhalt des Ordners <span.*>(?P<N>.+)</span></div>'
-
-
- def prepare(self):
- super(NetfolderIn, self).prepare()
-
- # 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"))
-
-
- 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 = m.group('ID')
- 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
-
-
-getInfo = create_getInfo(NetfolderIn)
diff --git a/module/plugins/crypter/NosvideoCom.py b/module/plugins/crypter/NosvideoCom.py
deleted file mode 100644
index 9808837c4..000000000
--- a/module/plugins/crypter/NosvideoCom.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class NosvideoCom(SimpleCrypter):
- __name__ = "NosvideoCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?nosvideo\.com/\?v=\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Nosvideo.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- LINK_PATTERN = r'href="(http://(?:w{3}\.)?nosupload\.com/\?d=\w+)"'
- NAME_PATTERN = r'<[tT]itle>Watch (?P<N>.+?)<'
-
-
-getInfo = create_getInfo(NosvideoCom)
diff --git a/module/plugins/crypter/OneKhDe.py b/module/plugins/crypter/OneKhDe.py
deleted file mode 100644
index 4dcb416f5..000000000
--- a/module/plugins/crypter/OneKhDe.py
+++ /dev/null
@@ -1,42 +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.11"
-
- __pattern__ = r'http://(?:www\.)?1kh\.de/f/'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """1kh.de decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
- def __init__(self, parent):
- Crypter.__init__(self, parent)
- self.parent = parent
-
-
- def file_exists(self):
- """ returns True or False
- """
- return True
-
-
- def proceed(self, url, location):
- url = self.parent.url
- self.html = self.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.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 100644
index 9e06bdf32..000000000
--- a/module/plugins/crypter/OronComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class OronComFolder(DeadCrypter):
- __name__ = "OronComFolder"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?oron\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Oron.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("DHMH", "webmaster@pcProfil.de")]
-
-
-getInfo = create_getInfo(OronComFolder)
diff --git a/module/plugins/crypter/PastebinCom.py b/module/plugins/crypter/PastebinCom.py
deleted file mode 100644
index 751b47cc9..000000000
--- a/module/plugins/crypter/PastebinCom.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class PastebinCom(SimpleCrypter):
- __name__ = "PastebinCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?pastebin\.com/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Pastebin.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- LINK_PATTERN = r'<div class="de\d+">(https?://[^ <]+)(?:[^<]*)</div>'
- NAME_PATTERN = r'<div class="paste_box_line1" title="(?P<N>.+?)">'
-
-
-getInfo = create_getInfo(PastebinCom)
diff --git a/module/plugins/crypter/QuickshareCzFolder.py b/module/plugins/crypter/QuickshareCzFolder.py
deleted file mode 100644
index 3e38d36b4..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.10"
-
- __pattern__ = r'http://(?:www\.)?quickshare\.cz/slozka-\d+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Quickshare.cz folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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.S)
- if m is None:
- self.error(_("FOLDER_PATTERN not found"))
- self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/module/plugins/crypter/RSLayerCom.py b/module/plugins/crypter/RSLayerCom.py
deleted file mode 100644
index cc3b23bbc..000000000
--- a/module/plugins/crypter/RSLayerCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class RSLayerCom(DeadCrypter):
- __name__ = "RSLayerCom"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?rs-layer\.com/directory-'
- __config__ = []
-
- __description__ = """RS-Layer.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("hzpz", None)]
-
-
-getInfo = create_getInfo(RSLayerCom)
diff --git a/module/plugins/crypter/RelinkUs.py b/module/plugins/crypter/RelinkUs.py
deleted file mode 100644
index ed3da1f9b..000000000
--- a/module/plugins/crypter/RelinkUs.py
+++ /dev/null
@@ -1,293 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import binascii
-import re
-import os
-
-from Crypto.Cipher import AES
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class RelinkUs(Crypter):
- __name__ = "RelinkUs"
- __type__ = "crypter"
- __version__ = "3.12"
-
- __pattern__ = r'http://(?:www\.)?relink\.us/(f/|((view|go)\.php\?id=))(?P<ID>.+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Relink.us decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("AndroKev", "neureither.kevin@gmail.com")]
-
-
- 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>(.*)</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\(\'(.+)\'\)'
- WEB_FORWARD_URL = r'http://www.relink.us/frame.php'
- WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(.+)"></iframe>'
-
-
- def setup(self):
- self.fileid = None
- self.package = 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)]
-
-
- def initPackage(self, pyfile):
- self.fileid = re.match(self.__pattern__, pyfile.url).group('ID')
- self.package = pyfile.package()
-
-
- 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):
- password = self.getPassword()
-
- self.logDebug("Submitting password [%s] for protected links" % password)
-
- if password:
- passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid
- passwd_data = {'id': self.fileid, 'password': 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:
- 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.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.error(_('Unknown source type "%s"') % source)
-
-
- def handleCNL2Links(self):
- self.logDebug("Search for CNL2 links")
- package_links = []
- m = re.search(self.CNL2_FORM_REGEX, self.html, re.S)
- if m:
- 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 Exception:
- 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:
- 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 = save_join(self.config['general']['download_folder'], dlc_filename)
- with open(dlc_filepath, "wb") as f:
- f.write(dlc)
- package_links.append(dlc_filepath)
-
- except Exception:
- self.fail(_("Unable to download DLC container"))
-
- return package_links
-
-
- def handleWEBLinks(self):
- self.logDebug("Search for WEB links")
-
- package_links = []
- params = re.findall(self.WEB_FORWARD_REGEX, self.html)
-
- self.logDebug("Decrypting %d Web links" % len(params))
-
- for index, param in enumerate(params):
- try:
- url = self.WEB_FORWARD_URL + "?%s" % param
-
- self.logDebug("Decrypting Web link %d, %s" % (index + 1, url))
-
- res = self.load(url, decode=True)
- link = re.search(self.WEB_LINK_REGEX, res).group(1)
-
- package_links.append(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.I)
-
- # Get crypted
- crypted_re = self.CNL2_FORMINPUT_REGEX % RelinkUs.CNL2_CRYPTED_KEY
- vcrypted = re.findall(crypted_re, cnl2_form, re.I)
-
- # 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)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # 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 7f0915065..000000000
--- a/module/plugins/crypter/SafelinkingNet.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from 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.14"
-
- __pattern__ = r'https?://(?:www\.)?safelinking\.net/([pd])/\w+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Safelinking.net decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("quareevo", "quareevo@arcor.de")]
-
-
- SOLVEMEDIA_PATTERN = "solvemediaApiKey = '([\w.-]+)';"
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
-
- if re.match(self.__pattern__, url).group(1) == "d":
-
- header = self.load(url, just_header=True)
- if 'location' in header:
- self.urls = [header['location']]
- else:
- self.error(_("Couldn't find forwarded Link"))
-
- else:
- postData = {"post-protect": "1"}
-
- self.html = self.load(url)
-
- if "link-password" in self.html:
- postData['link-password'] = self.getPassword()
-
- if "altcaptcha" in self.html:
- for _i 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"))
-
- response, challenge = 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 cbfa919ac..000000000
--- a/module/plugins/crypter/SecuredIn.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class SecuredIn(DeadCrypter):
- __name__ = "SecuredIn"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?secured\.in/download-[\d]+-\w{8}\.html'
- __config__ = []
-
- __description__ = """Secured.in decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(SecuredIn)
diff --git a/module/plugins/crypter/SexuriaCom.py b/module/plugins/crypter/SexuriaCom.py
deleted file mode 100644
index 06dcf547e..000000000
--- a/module/plugins/crypter/SexuriaCom.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class SexuriaCom(Crypter):
- __name__ = "SexuriaCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sexuria\.com/(v1/)?(Pornos_Kostenlos_.+?_(\d+)\.html|dl_links_\d+_\d+\.html|id=\d+\&part=\d+\&link=\d+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Sexuria.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("NETHead", "NETHead.AT.gmx.DOT.net")]
-
-
- PATTERN_SUPPORTED_MAIN = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?Pornos_Kostenlos_.+?_(\d+)\.html', re.I)
- PATTERN_SUPPORTED_CRYPT = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?dl_links_\d+_(?P<ID>\d+)\.html', re.I)
- PATTERN_SUPPORTED_REDIRECT = re.compile(r'http://(www\.)?sexuria\.com/out\.php\?id=(?P<ID>\d+)\&part=\d+\&link=\d+', re.I)
- PATTERN_TITLE = re.compile(r'<title> - (?P<TITLE>.*) Sexuria - Kostenlose Pornos - Rapidshare XXX Porn</title>', re.I)
- PATTERN_PASSWORD = re.compile(r'<strong>Passwort: </strong></div></td>.*?bgcolor="#EFEFEF">(?P<PWD>.*?)</td>', re.I | re.S)
- PATTERN_DL_LINK_PAGE = re.compile(r'"(dl_links_\d+_\d+\.html)"', re.I)
- PATTERN_REDIRECT_LINKS = re.compile(r'value="(http://sexuria\.com/out\.php\?id=\d+\&part=\d+\&link=\d+)" readonly', re.I)
-
-
- def decrypt(self, pyfile):
- # Init
- self.pyfile = pyfile
- self.package = pyfile.package()
-
- # Get package links
- package_name, self.links, folder_name, package_pwd = self.decryptLinks(self.pyfile.url)
- self.packages = [(package_name, self.links, folder_name)]
-
-
- def decryptLinks(self, url):
- linklist = []
- name = self.package.name
- folder = self.package.folder
- password = None
-
- if re.match(self.PATTERN_SUPPORTED_MAIN, url):
- # Processing main page
- html = self.load(url)
- links = re.findall(self.PATTERN_DL_LINK_PAGE, html)
- for link in links:
- linklist.append("http://sexuria.com/v1/" + link)
-
- elif re.match(self.PATTERN_SUPPORTED_REDIRECT, url):
- # Processing direct redirect link (out.php), redirecting to main page
- id = re.search(self.PATTERN_SUPPORTED_REDIRECT, url).group('ID')
- if id:
- linklist.append("http://sexuria.com/v1/Pornos_Kostenlos_liebe_%s.html" % id)
-
- elif re.match(self.PATTERN_SUPPORTED_CRYPT, url):
- # Extract info from main file
- id = re.search(self.PATTERN_SUPPORTED_CRYPT, url).group('ID')
- html = self.load("http://sexuria.com/v1/Pornos_Kostenlos_info_%s.html" % id, decode=True)
-
- title = re.search(self.PATTERN_TITLE, html).group('TITLE').strip()
- if title:
- name = folder = title
- self.logDebug("Package info found, name [%s] and folder [%s]" % (name, folder))
-
- pwd = re.search(self.PATTERN_PASSWORD, html).group('PWD')
- if pwd:
- password = pwd.strip()
- self.logDebug("Password info [%s] found" % password)
-
- # Process link (dl_link)
- html = self.load(url)
- links = re.findall(self.PATTERN_REDIRECT_LINKS, html)
- if len(links) == 0:
- self.LogError("Broken for link %s" % link)
- else:
- for link in links:
- link = link.replace("http://sexuria.com/", "http://www.sexuria.com/")
- finallink = self.load(link, just_header=True)['location']
- if not finallink or "sexuria.com/" in finallink:
- self.LogError("Broken for link %s" % link)
- else:
- linklist.append(finallink)
-
- # Debug log
- self.logDebug("%d supported links" % len(linklist))
- for i, link in enumerate(linklist):
- self.logDebug("Supported link %d, %s" % (i + 1, link))
-
- return name, linklist, folder, password
diff --git a/module/plugins/crypter/ShareLinksBiz.py b/module/plugins/crypter/ShareLinksBiz.py
deleted file mode 100644
index f17ba54d7..000000000
--- a/module/plugins/crypter/ShareLinksBiz.py
+++ /dev/null
@@ -1,279 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import binascii
-import re
-
-from Crypto.Cipher import AES
-from module.plugins.Crypter import Crypter
-
-
-class ShareLinksBiz(Crypter):
- __name__ = "ShareLinksBiz"
- __type__ = "crypter"
- __version__ = "1.14"
-
- __pattern__ = r'http://(?:www\.)?(share-links|s2l)\.biz/(?P<ID>_?\w+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Share-Links.biz decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es")]
-
-
- def setup(self):
- self.baseUrl = None
- self.fileId = None
- self.package = 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.invalidCaptcha()
- self.retry(wait_time=5)
- 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.iteritems():
- 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.invalidCaptcha()
- self.retry(wait_time=5)
- 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.S)
- if m:
- 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
- res = self.load(dwLink)
-
- code = re.search(r'frm/(\d+)', res).group(1)
- fwLink = self.baseUrl + "/get/frm/" + code
- res = self.load(fwLink)
-
- jscode = re.search(r'<script language="javascript">\s*eval\((.*)\)\s*</script>', res, re.S).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 Exception:
- 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)
- res = self.load(url)
- params = res.split(";;")
-
- # Get jk
- strlist = list(params[1].decode('base64'))
- jk = ''.join(strlist[::-1])
-
- # Get crypted
- strlist = list(params[2].decode('base64'))
- crypted = ''.join(strlist[::-1])
-
- # 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)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted.decode('base64'))
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = filter(bool, text.split('\n'))
-
- # Log and return
- self.logDebug("Block has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/SharingmatrixComFolder.py b/module/plugins/crypter/SharingmatrixComFolder.py
deleted file mode 100644
index 4448dd691..000000000
--- a/module/plugins/crypter/SharingmatrixComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class SharingmatrixComFolder(DeadCrypter):
- __name__ = "SharingmatrixComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sharingmatrix\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Sharingmatrix.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(SharingmatrixComFolder)
diff --git a/module/plugins/crypter/SpeedLoadOrgFolder.py b/module/plugins/crypter/SpeedLoadOrgFolder.py
deleted file mode 100644
index ddde7dec2..000000000
--- a/module/plugins/crypter/SpeedLoadOrgFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class SpeedLoadOrgFolder(DeadCrypter):
- __name__ = "SpeedLoadOrgFolder"
- __type__ = "crypter"
- __version__ = "0.30"
-
- __pattern__ = r'http://(?:www\.)?speedload\.org/(\d+~f$|folder/\d+/)'
- __config__ = []
-
- __description__ = """Speedload decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(SpeedLoadOrgFolder)
diff --git a/module/plugins/crypter/StealthTo.py b/module/plugins/crypter/StealthTo.py
deleted file mode 100644
index 5173421f1..000000000
--- a/module/plugins/crypter/StealthTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class StealthTo(DeadCrypter):
- __name__ = "StealthTo"
- __type__ = "crypter"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?stealth\.to/folder/.+'
- __config__ = []
-
- __description__ = """Stealth.to decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
-getInfo = create_getInfo(StealthTo)
diff --git a/module/plugins/crypter/TnyCz.py b/module/plugins/crypter/TnyCz.py
deleted file mode 100644
index 719c26449..000000000
--- a/module/plugins/crypter/TnyCz.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-import re
-
-
-class TnyCz(SimpleCrypter):
- __name__ = "TnyCz"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?tny\.cz/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Tny.cz decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<title>(?P<N>.+) - .+</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
-
-
-getInfo = create_getInfo(TnyCz)
diff --git a/module/plugins/crypter/TrailerzoneInfo.py b/module/plugins/crypter/TrailerzoneInfo.py
deleted file mode 100644
index 10780dd45..000000000
--- a/module/plugins/crypter/TrailerzoneInfo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class TrailerzoneInfo(DeadCrypter):
- __name__ = "TrailerzoneInfo"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?trailerzone\.info/.+'
- __config__ = []
-
- __description__ = """TrailerZone.info decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
-getInfo = create_getInfo(TrailerzoneInfo)
diff --git a/module/plugins/crypter/TurbobitNetFolder.py b/module/plugins/crypter/TurbobitNetFolder.py
deleted file mode 100644
index dcbd6a5d7..000000000
--- a/module/plugins/crypter/TurbobitNetFolder.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-from module.common.json_layer import json_loads
-
-
-class TurbobitNetFolder(SimpleCrypter):
- __name__ = "TurbobitNetFolder"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?turbobit\.net/download/folder/(?P<ID>\w+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Turbobit.net folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'src=\'/js/lib/grid/icon/folder.png\'> <span>(?P<N>.+?)</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))
-
-
-getInfo = create_getInfo(TurbobitNetFolder)
diff --git a/module/plugins/crypter/TusfilesNetFolder.py b/module/plugins/crypter/TusfilesNetFolder.py
deleted file mode 100644
index 1cc505b81..000000000
--- a/module/plugins/crypter/TusfilesNetFolder.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import math
-import re
-import urlparse
-
-from module.plugins.internal.XFSCrypter import XFSCrypter, create_getInfo
-
-
-class TusfilesNetFolder(XFSCrypter):
- __name__ = "TusfilesNetFolder"
- __type__ = "crypter"
- __version__ = "0.08"
-
- __pattern__ = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Tusfiles.net folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- PAGES_PATTERN = r'>\((\d+) \w+\)<'
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'https://www.tusfiles.net/go/\g<ID>/')]
-
-
- def loadPage(self, page_n):
- return self.load(urlparse.urljoin(self.pyfile.url, str(page_n)), decode=True)
-
-
- def handlePages(self, pyfile):
- 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.links += self.getLinks()
-
-
-getInfo = create_getInfo(TusfilesNetFolder)
diff --git a/module/plugins/crypter/UlozToFolder.py b/module/plugins/crypter/UlozToFolder.py
deleted file mode 100644
index 3f2a60cf7..000000000
--- a/module/plugins/crypter/UlozToFolder.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class UlozToFolder(Crypter):
- __name__ = "UlozToFolder"
- __type__ = "crypter"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(m|soubory)/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Uloz.to folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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.S)
- if m is None:
- self.error(_("FOLDER_PATTERN not found"))
-
- 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)]
diff --git a/module/plugins/crypter/UploadableChFolder.py b/module/plugins/crypter/UploadableChFolder.py
deleted file mode 100644
index f1eb93323..000000000
--- a/module/plugins/crypter/UploadableChFolder.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class UploadableChFolder(SimpleCrypter):
- __name__ = "UploadableChFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?uploadable\.ch/list/\w+'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """ Uploadable.ch folder decrypter plugin """
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LINK_PATTERN = r'"(.+?)" class="icon_zipfile">'
- NAME_PATTERN = r'<div class="folder"><span>&nbsp;</span>(?P<N>.+?)</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">'
-
-
-getInfo = create_getInfo(UploadableChFolder)
diff --git a/module/plugins/crypter/UploadedToFolder.py b/module/plugins/crypter/UploadedToFolder.py
deleted file mode 100644
index d54ed47eb..000000000
--- a/module/plugins/crypter/UploadedToFolder.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class UploadedToFolder(SimpleCrypter):
- __name__ = "UploadedToFolder"
- __type__ = "crypter"
- __version__ = "0.42"
-
- __pattern__ = r'http://(?:www\.)?(uploaded|ul)\.(to|net)/(f|folder|list)/(?P<ID>\w+)'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """UploadedTo decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- PLAIN_PATTERN = r'<small class="date"><a href="([\w/]+)" onclick='
- NAME_PATTERN = r'<title>(?P<N>.+?)<'
-
-
- def getLinks(self):
- m = re.search(self.PLAIN_PATTERN, self.html)
- if m is None:
- self.error(_("PLAIN_PATTERN not found"))
-
- plain_link = urlparse.urljoin("http://uploaded.net/", m.group(1))
- return self.load(plain_link).split('\n')[:-1]
-
-
-getInfo = create_getInfo(UploadedToFolder)
diff --git a/module/plugins/crypter/WiiReloadedOrg.py b/module/plugins/crypter/WiiReloadedOrg.py
deleted file mode 100644
index c3c5b8222..000000000
--- a/module/plugins/crypter/WiiReloadedOrg.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class WiiReloadedOrg(DeadCrypter):
- __name__ = "WiiReloadedOrg"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?wii-reloaded\.org/protect/get\.php\?i=.+'
- __config__ = []
-
- __description__ = """Wii-Reloaded.org decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("hzpz", None)]
-
-
-getInfo = create_getInfo(WiiReloadedOrg)
diff --git a/module/plugins/crypter/WuploadComFolder.py b/module/plugins/crypter/WuploadComFolder.py
deleted file mode 100644
index c4171667e..000000000
--- a/module/plugins/crypter/WuploadComFolder.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class WuploadComFolder(DeadCrypter):
- __name__ = "WuploadComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?wupload\.com/folder/\w+'
- __config__ = []
-
- __description__ = """Wupload.com folder decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(WuploadComFolder)
diff --git a/module/plugins/crypter/XFileSharingProFolder.py b/module/plugins/crypter/XFileSharingProFolder.py
deleted file mode 100644
index 8b850271d..000000000
--- a/module/plugins/crypter/XFileSharingProFolder.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSCrypter import XFSCrypter, create_getInfo
-
-
-class XFileSharingProFolder(XFSCrypter):
- __name__ = "XFileSharingProFolder"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """XFileSharingPro dummy folder decrypter plugin for hook"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def _log(self, type, args):
- msg = " | ".join(str(a).strip() for a in args if a)
- logger = getattr(self.log, type)
- logger("%s: %s: %s" % (self.__name__, self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
-
-
- def init(self):
- super(XFileSharingProFolder, self).init()
-
- self.__pattern__ = self.core.pluginManager.crypterPlugins[self.__name__]['pattern']
-
- self.HOSTER_DOMAIN = re.match(self.__pattern__, self.pyfile.url).group("DOMAIN").lower()
- self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
-
- account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
-
- if account and account.canUse():
- self.account = account
-
- elif self.account:
- self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
-
- else:
- return
-
- self.user, data = self.account.selectAccount()
- self.req = self.account.getAccountRequest(self.user)
- self.premium = self.account.isPremium(self.user)
-
-
-getInfo = create_getInfo(XFileSharingProFolder)
diff --git a/module/plugins/crypter/XupPl.py b/module/plugins/crypter/XupPl.py
deleted file mode 100644
index 5ab6750c4..000000000
--- a/module/plugins/crypter/XupPl.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Crypter import Crypter
-
-
-class XupPl(Crypter):
- __name__ = "XupPl"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = r'https?://(?:[^/]*\.)?xup\.pl/.+'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Xup.pl decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "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/YoutubeComFolder.py b/module/plugins/crypter/YoutubeComFolder.py
deleted file mode 100644
index a2b02b2f1..000000000
--- a/module/plugins/crypter/YoutubeComFolder.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class YoutubeComFolder(Crypter):
- __name__ = "YoutubeComFolder"
- __type__ = "crypter"
- __version__ = "1.01"
-
- __pattern__ = r'https?://(?:www\.|m\.)?youtube\.com/(?P<TYPE>user|playlist|view_play_list)(/|.*?[?&](?:list|p)=)(?P<ID>[\w-]+)'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True ),
- ("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"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- API_KEY = "AIzaSyCKnWLNlkX-L4oD1aEzqqhRw1zczeD6_k0"
-
-
- def api_response(self, ref, req):
- req.update({"key": self.API_KEY})
- url = urlparse.urljoin("https://www.googleapis.com/youtube/v3/", ref)
- html = self.load(url, get=req)
- return json_loads(html)
-
-
- 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/crypter/__init__.py b/module/plugins/crypter/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/crypter/__init__.py
+++ /dev/null
diff --git a/module/plugins/hooks/AlldebridComHook.py b/module/plugins/hooks/AlldebridComHook.py
deleted file mode 100644
index 092921134..000000000
--- a/module/plugins/hooks/AlldebridComHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class AlldebridComHook(MultiHook):
- __name__ = "AlldebridComHook"
- __type__ = "hook"
- __version__ = "0.16"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
- ("ssl" , "bool" , "Use HTTPS" , True )]
-
- __description__ = """Alldebrid.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt", "spamsales@online.de")]
-
-
- def getHosters(self):
- https = "https" if self.getConfig('ssl') else "http"
- html = self.getURL(https + "://www.alldebrid.com/api.php", get={'action': "get_host"}).replace("\"", "").strip()
-
- return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/module/plugins/hooks/AndroidPhoneNotify.py b/module/plugins/hooks/AndroidPhoneNotify.py
deleted file mode 100644
index 67a0ea4f0..000000000
--- a/module/plugins/hooks/AndroidPhoneNotify.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Hook, Expose
-
-
-class AndroidPhoneNotify(Hook):
- __name__ = "AndroidPhoneNotify"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("apikey" , "str" , "API key" , "" ),
- ("notifycaptcha" , "bool", "Notify captcha request" , True ),
- ("notifypackage" , "bool", "Notify package finished" , True ),
- ("notifyprocessed", "bool", "Notify packages processed" , True ),
- ("notifyupdate" , "bool", "Notify plugin updates" , True ),
- ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
- ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
- ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
- ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
-
- __description__ = """Send push notifications to your Android Phone (using notifymyandroid.com)"""
- __license__ = "GPLv3"
- __authors__ = [("Steven Kosyra" , "steven.kosyra@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- event_list = ["allDownloadsProcessed", "plugin_updated"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.last_notify = 0
- self.notifications = 0
-
-
- def plugin_updated(self, type_plugins):
- if not self.getConfig('notifyupdate'):
- return
-
- self.notify(_("Plugins updated"), str(type_plugins))
-
-
- def coreExiting(self):
- if not self.getConfig('notifyexit'):
- return
-
- if self.core.do_restart:
- self.notify(_("Restarting pyLoad"))
- else:
- self.notify(_("Exiting pyLoad"))
-
-
- def newCaptchaTask(self, task):
- if not self.getConfig('notifycaptcha'):
- return
-
- self.notify(_("Captcha"), _("New request waiting user input"))
-
-
- def packageFinished(self, pypack):
- if self.getConfig('notifypackage'):
- self.notify(_("Package finished"), pypack.name)
-
-
- def allDownloadsProcessed(self):
- if not self.getConfig('notifyprocessed'):
- return
-
- if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
- self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
- else:
- self.notify(_("All packages finished"))
-
-
- @Expose
- def notify(self,
- event,
- msg="",
- key=self.getConfig('apikey')):
-
- if not key:
- return
-
- if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
- return
-
- elapsed_time = time.time() - self.last_notify
-
- if elapsed_time < self.getConf("sendtimewait"):
- return
-
- if elapsed_time > 60:
- self.notifications = 0
-
- elif self.notifications >= self.getConf("sendpermin"):
- return
-
-
- getURL("http://www.notifymyandroid.com/publicapi/notify",
- get={'apikey' : key,
- 'application': "pyLoad",
- 'event' : event,
- 'description': msg})
-
- self.last_notify = time.time()
- self.notifications += 1
diff --git a/module/plugins/hooks/AntiVirus.py b/module/plugins/hooks/AntiVirus.py
deleted file mode 100644
index c620f556d..000000000
--- a/module/plugins/hooks/AntiVirus.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import shutil
-import subprocess
-
-try:
- import send2trash
-except ImportError:
- pass
-
-from module.plugins.Hook import Hook, Expose, threaded
-from module.utils import fs_encode, save_join
-
-
-class AntiVirus(Hook):
- __name__ = "AntiVirus"
- __type__ = "hook"
- __version__ = "0.09"
-
- #@TODO: add trash option (use Send2Trash lib)
- __config__ = [("action" , "Antivirus default;Delete;Quarantine", "Manage infected files" , "Antivirus default"),
- ("quardir" , "folder" , "Quarantine folder" , "" ),
- ("deltotrash", "bool" , "Move to trash (recycle bin) instead delete", True ),
- ("scanfailed", "bool" , "Scan incompleted files (failed downloads)" , False ),
- ("cmdfile" , "file" , "Antivirus executable" , "" ),
- ("cmdargs" , "str" , "Scan options" , "" ),
- ("ignore-err", "bool" , "Ignore scan errors" , False )]
-
- __description__ = """Scan downloaded files with antivirus program"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- @Expose
- @threaded
- def scan(self, pyfile, thread):
- file = fs_encode(pyfile.plugin.lastDownload)
- filename = os.path.basename(pyfile.plugin.lastDownload)
- cmdfile = fs_encode(self.getConfig('cmdfile'))
- cmdargs = fs_encode(self.getConfig('cmdargs').strip())
-
- if not os.path.isfile(file) or not os.path.isfile(cmdfile):
- return
-
- thread.addActive(pyfile)
- pyfile.setCustomStatus(_("virus scanning"))
- pyfile.setProgress(0)
-
- try:
- p = subprocess.Popen([cmdfile, cmdargs, file], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
- out, err = map(str.strip, p.communicate())
-
- if out:
- self.logInfo(filename, out)
-
- if err:
- self.logWarning(filename, err)
- if not self.getConfig('ignore-err'):
- self.logDebug("Delete/Quarantine task is aborted")
- return
-
- if p.returncode:
- pyfile.error = _("infected file")
- action = self.getConfig('action')
- try:
- if action == "Delete":
- if not self.getConfig('deltotrash'):
- os.remove(file)
-
- else:
- try:
- send2trash.send2trash(file)
-
- except NameError:
- self.logWarning(_("Send2Trash lib not found, moving to quarantine instead"))
- pyfile.setCustomStatus(_("file moving"))
- shutil.move(file, self.getConfig('quardir'))
-
- except Exception, e:
- self.logWarning(_("Unable to move file to trash: %s, moving to quarantine instead") % e.message)
- pyfile.setCustomStatus(_("file moving"))
- shutil.move(file, self.getConfig('quardir'))
-
- else:
- self.logDebug(_("Successfully moved file to trash"))
-
- elif action == "Quarantine":
- pyfile.setCustomStatus(_("file moving"))
- shutil.move(file, self.getConfig('quardir'))
-
- except (IOError, shutil.Error), e:
- self.logError(filename, action + " action failed!", e)
-
- elif not out and not err:
- self.logDebug(filename, "No infected file found")
-
- finally:
- pyfile.setProgress(100)
- thread.finishFile(pyfile)
-
-
- def downloadFinished(self, pyfile):
- return self.scan(pyfile)
-
-
- def downloadFailed(self, pyfile):
- #: Check if pyfile is still "failed",
- # maybe might has been restarted in meantime
- if pyfile.status == 8 and self.getConfig('scanfailed'):
- return self.scan(pyfile)
diff --git a/module/plugins/hooks/BypassCaptcha.py b/module/plugins/hooks/BypassCaptcha.py
deleted file mode 100644
index 9e01edfa2..000000000
--- a/module/plugins/hooks/BypassCaptcha.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-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.06"
-
- __config__ = [("force", "bool", "Force BC even if client is connected", False),
- ("passkey", "password", "Passkey", "")]
-
- __description__ = """Send captchas to BypassCaptcha.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("Godofdream", "soilfcition@gmail.com"),
- ("zoidberg" , "zoidberg@mujmail.cz" )]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- 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 = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.GETCREDITS_URL, post={"key": self.getConfig('passkey')})
-
- data = dict(x.split(' ', 1) for x in res.splitlines())
- return int(data['Left'])
-
-
- def submit(self, captcha, captchaType="file", match=None):
- req = getRequest()
-
- #raise timeout threshold
- req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
-
- try:
- res = req.load(self.SUBMIT_URL,
- post={'vendor_key': self.PYLOAD_KEY,
- 'key': self.getConfig('passkey'),
- 'gen_task_id': "1",
- 'file': (pycurl.FORM_FILE, captcha)},
- multipart=True)
- finally:
- req.close()
-
- data = dict(x.split(' ', 1) for x in res.splitlines())
- if not data or "Value" not in data:
- raise BypassCaptchaException(res)
-
- result = data['Value']
- ticket = data['TaskId']
- self.logDebug("Result %s : %s" % (ticket, result))
-
- return ticket, result
-
-
- def respond(self, ticket, success):
- try:
- res = 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"), 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)
- 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)
-
-
- @threaded
- 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 100644
index 60482b8fc..000000000
--- a/module/plugins/hooks/Captcha9Kw.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-import time
-
-from base64 import b64encode
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL
-
-from module.plugins.Hook import Hook, threaded
-
-
-class Captcha9Kw(Hook):
- __name__ = "Captcha9Kw"
- __type__ = "hook"
- __version__ = "0.28"
-
- __config__ = [("ssl" , "bool" , "Use HTTPS" , True ),
- ("force" , "bool" , "Force captcha resolving even if client is connected" , True ),
- ("confirm" , "bool" , "Confirm Captcha (cost +6 credits)" , False ),
- ("captchaperhour", "int" , "Captcha per hour" , "9999" ),
- ("captchapermin" , "int" , "Captcha per minute" , "9999" ),
- ("prio" , "int" , "Priority (max 10)(cost +0 -> +10 credits)" , "0" ),
- ("queue" , "int" , "Max. Queue (max 999)" , "50" ),
- ("hoster_options", "string" , "Hoster options (format: pluginname:prio=1:selfsolfe=1:confirm=1:timeout=900|...)", "ShareonlineBiz:prio=0:timeout=999 | UploadedTo:prio=0:timeout=999"),
- ("selfsolve" , "bool" , "Selfsolve (manually solve your captcha in your 9kw client if active)" , "0" ),
- ("passkey" , "password", "API key" , "" ),
- ("timeout" , "int" , "Timeout in seconds (min 60, max 3999)" , "900" )]
-
- __description__ = """Send captchas to 9kw.eu"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://www.9kw.eu/index.cgi"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- if self.getConfig('ssl'):
- self.API_URL = self.API_URL.replace("http://", "https://")
-
-
- def getCredits(self):
- res = getURL(self.API_URL,
- get={'apikey': self.getConfig('passkey'),
- 'pyload': "1",
- 'source': "pyload",
- 'action': "usercaptchaguthaben"})
-
- if res.isdigit():
- self.logInfo(_("%s credits left") % res)
- credits = self.info['credits'] = int(res)
- return credits
- else:
- self.logError(res)
- return 0
-
-
- @threaded
- def _processCaptcha(self, task):
- try:
- with open(task.captchaFile, 'rb') as f:
- data = f.read()
-
- except IOError, e:
- self.logError(e)
- return
-
- pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
- option = {'min' : 2,
- 'max' : 50,
- 'phrase' : 0,
- 'numeric' : 0,
- 'case_sensitive': 0,
- 'math' : 0,
- 'prio' : min(max(self.getConfig('prio'), 0), 10),
- 'confirm' : self.getConfig('confirm'),
- 'timeout' : min(max(self.getConfig('timeout'), 300), 3999),
- 'selfsolve' : self.getConfig('selfsolve'),
- 'cph' : self.getConfig('captchaperhour'),
- 'cpm' : self.getConfig('captchapermin')}
-
- for opt in str(self.getConfig('hoster_options').split('|')):
-
- details = map(str.strip, opt.split(':'))
-
- if not details or details[0].lower() != pluginname.lower():
- continue
-
- for d in details:
- hosteroption = d.split("=")
-
- if len(hosteroption) < 2 or not hosteroption[1].isdigit():
- continue
-
- o = hosteroption[0].lower()
- if o in option:
- option[o] = hosteroption[1]
-
- break
-
- post_data = {'apikey' : self.getConfig('passkey'),
- 'prio' : option['prio'],
- 'confirm' : option['confirm'],
- 'maxtimeout' : option['timeout'],
- 'selfsolve' : option['selfsolve'],
- 'captchaperhour': option['cph'],
- 'captchapermin' : option['cpm'],
- 'case-sensitive': option['case_sensitive'],
- 'min_len' : option['min'],
- 'max_len' : option['max'],
- 'phrase' : option['phrase'],
- 'numeric' : option['numeric'],
- 'math' : option['math'],
- 'oldsource' : pluginname,
- 'pyload' : "1",
- 'source' : "pyload",
- 'base64' : "1",
- 'mouse' : 1 if task.isPositional() else 0,
- 'file-upload-01': b64encode(data),
- 'action' : "usercaptchaupload"}
-
- for _i in xrange(5):
- try:
- res = getURL(self.API_URL, post=post_data)
- except BadHeader, e:
- time.sleep(3)
- else:
- if res and res.isdigit():
- break
- else:
- self.logError(_("Bad upload: %s") % res)
- return
-
- self.logDebug(_("NewCaptchaID ticket: %s") % res, task.captchaFile)
-
- task.data["ticket"] = res
-
- for _i in xrange(int(self.getConfig('timeout') / 5)):
- result = getURL(self.API_URL,
- get={'apikey': self.getConfig('passkey'),
- 'id' : res,
- 'pyload': "1",
- 'info' : "1",
- 'source': "pyload",
- 'action': "usercaptchacorrectdata"})
-
- if not result or result == "NO DATA":
- time.sleep(5)
- else:
- break
- else:
- self.logDebug("Could not send request: %s" % res)
- result = None
-
- self.logInfo(_("Captcha result for ticket %s: %s") % (res, result))
-
- task.setResult(result)
-
-
- def newCaptchaTask(self, task):
- if not task.isTextual() and not task.isPositional():
- return
-
- if not self.getConfig('passkey'):
- return
-
- if self.core.isClientConnected() and not self.getConfig('force'):
- return
-
- credits = self.getCredits()
-
- if not credits:
- self.logError(_("Your captcha 9kw.eu account has not enough credits"))
- return
-
- queue = min(self.getConfig('queue'), 999)
- timeout = min(max(self.getConfig('timeout'), 300), 3999)
- pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
-
- for _i in xrange(5):
- servercheck = getURL("http://www.9kw.eu/grafik/servercheck.txt")
- if queue < re.search(r'queue=(\d+)', servercheck).group(1):
- break
-
- time.sleep(10)
- else:
- self.fail(_("Too many captchas in queue"))
-
- for opt in str(self.getConfig('hoster_options').split('|')):
- details = map(str.strip, opt.split(':'))
-
- if not details or details[0].lower() != pluginname.lower():
- continue
-
- for d in details:
- hosteroption = d.split("=")
-
- if len(hosteroption) > 1 \
- and hosteroption[0].lower() == 'timeout' \
- and hosteroption[1].isdigit():
- timeout = int(hosteroption[1])
-
- break
-
- task.handler.append(self)
-
- task.setWaiting(timeout)
-
- self._processCaptcha(task)
-
-
- def _captchaResponse(self, task, correct):
- type = "correct" if correct else "refund"
-
- if 'ticket' not in task.data:
- self.logDebug("No CaptchaID for %s request (task: %s)" % (type, task))
- return
-
- passkey = self.getConfig('passkey')
-
- for _i in xrange(3):
- res = getURL(self.API_URL,
- get={'action' : "usercaptchacorrectback",
- 'apikey' : passkey,
- 'api_key': passkey,
- 'correct': "1" if correct else "2",
- 'pyload' : "1",
- 'source' : "pyload",
- 'id' : task.data["ticket"]})
-
- self.logDebug("Request %s: %s" % (type, res))
-
- if res == "OK":
- break
-
- time.sleep(5)
- else:
- self.logDebug("Could not send %s request: %s" % (type, res))
-
-
- def captchaCorrect(self, task):
- self._captchaResponse(task, True)
-
-
- def captchaInvalid(self, task):
- self._captchaResponse(task, False)
diff --git a/module/plugins/hooks/CaptchaBrotherhood.py b/module/plugins/hooks/CaptchaBrotherhood.py
deleted file mode 100644
index bda080037..000000000
--- a/module/plugins/hooks/CaptchaBrotherhood.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import StringIO
-import pycurl
-import time
-import urllib
-
-try:
- from PIL import Image
-except ImportError:
- import Image
-
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-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.08"
-
- __config__ = [("username", "str", "Username", ""),
- ("force", "bool", "Force CT even if client is connected", False),
- ("passkey", "password", "Password", "")]
-
- __description__ = """Send captchas to CaptchaBrotherhood.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://www.captchabrotherhood.com/"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.API_URL + "askCredits.aspx",
- get={"username": self.getConfig('username'), "password": self.getConfig('passkey')})
- if not res.startswith("OK"):
- raise CaptchaBrotherhoodException(res)
- else:
- credits = int(res[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,
- urllib.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()
- res = req.getResponse()
- except Exception, e:
- raise CaptchaBrotherhoodException("Submit captcha image failed")
-
- req.close()
-
- if not res.startswith("OK"):
- raise CaptchaBrotherhoodException(res[1])
-
- ticket = res[3:]
-
- for _i in xrange(15):
- time.sleep(5)
- res = self.api_response("askCaptchaResult", ticket)
- if res.startswith("OK-answered"):
- return ticket, res[12:]
-
- raise CaptchaBrotherhoodException("No solution received in time")
-
-
- def api_response(self, api, ticket):
- res = getURL("%s%s.aspx" % (self.API_URL, api),
- get={"username": self.getConfig('username'),
- "password": self.getConfig('passkey'),
- "captchaID": ticket})
- if not res.startswith("OK"):
- raise CaptchaBrotherhoodException("Unknown response: %s" % res)
-
- return res
-
-
- 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)
- 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:
- res = self.api_response("complainCaptcha", task.data['ticket'])
-
-
- @threaded
- 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 ab7daf701..000000000
--- a/module/plugins/hooks/Checksum.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import hashlib
-import os
-import re
-import zlib
-
-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), ''):
- 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), ''):
- last = hf(chunk, last)
-
- return "%x" % last
-
- else:
- return None
-
-
-class Checksum(Hook):
- __name__ = "Checksum"
- __type__ = "hook"
- __version__ = "0.16"
-
- __config__ = [("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"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com" ),
- ("stickell" , "l.stickell@yahoo.it")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
- 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.info = {} #@TODO: Remove in 0.4.10
- 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()
-
- elif hasattr(pyfile.plugin, "info") and isinstance(pyfile.plugin.info, dict):
- data = pyfile.plugin.info.copy()
- data.pop('size', None) #@NOTE: Don't check file size until a similary matcher will be implemented
-
- 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 os.path.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 = os.path.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")
-
- data.pop('size', None)
-
- # validate checksum
- if data and self.getConfig('check_checksum'):
-
- if not 'md5' in data:
- for type in ("checksum", "hashsum", "hash"):
- if type in data:
- data['md5'] = data[type] #@NOTE: What happens if it's not an md5 hash?
- break
-
- 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"), key.upper())
- else:
- self.logWarning(_("Unable to validate checksum for file: ") + 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:
- os.remove(local_file)
- pyfile.plugin.retry(max_tries, self.getConfig('wait_time'), 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 = os.path.splitext(link['name'])[1][1:].lower()
-
- if file_type not in self.formats:
- continue
-
- hash_file = fs_encode(save_join(download_folder, link['name']))
- if not os.path.isfile(hash_file):
- self.logWarning(_("File not found"), 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 a7862045e..000000000
--- a/module/plugins/hooks/ClickAndLoad.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import socket
-import time
-
-try:
- import ssl
-except ImportError:
- pass
-
-from threading import Lock
-
-from module.plugins.Hook import Hook, threaded
-
-
-def forward(source, destination):
- try:
- bufsize = 1024
- bufdata = source.recv(bufsize)
- while bufdata:
- destination.sendall(bufdata)
- bufdata = source.recv(bufsize)
- finally:
- destination.shutdown(socket.SHUT_WR)
- # destination.close()
-
-
-#@TODO: IPv6 support
-class ClickAndLoad(Hook):
- __name__ = "ClickAndLoad"
- __type__ = "hook"
- __version__ = "0.43"
-
- __config__ = [("activated", "bool", "Activated" , True),
- ("port" , "int" , "Port" , 9666),
- ("extern" , "bool", "Listen on the public network interface", True)]
-
- __description__ = """Click'n'Load hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.de" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def coreReady(self):
- if not self.config['webinterface']['activated']:
- return
-
- ip = "" if self.getConfig('extern') else "127.0.0.1"
- webport = self.config['webinterface']['port']
- cnlport = self.getConfig('port')
-
- self.proxy(ip, webport, cnlport)
-
-
- @threaded
- def proxy(self, ip, webport, cnlport):
- time.sleep(10) #@TODO: Remove in 0.4.10 (implement addon delay on startup)
-
- self.logInfo(_("Proxy listening on %s:%s") % (ip or "0.0.0.0", cnlport))
-
- self._server(ip, webport, cnlport)
-
- lock = Lock()
- lock.acquire()
- lock.acquire()
-
-
- @threaded
- def _server(self, ip, webport, cnlport):
- try:
- dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- dock_socket.bind((ip, cnlport))
- dock_socket.listen(5)
-
- while True:
- client_socket, client_addr = dock_socket.accept()
- self.logDebug("Connection from %s:%s" % client_addr)
-
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
- if self.config['webinterface']['https']:
- try:
- server_socket = ssl.wrap_socket(server_socket)
-
- except NameError:
- self.logError(_("pyLoad's webinterface is configured to use HTTPS, Please install python's ssl lib or disable HTTPS"))
- client_socket.close() #: reset the connection.
- continue
-
- except Exception, e:
- self.logError(_("SSL error: %s") % e.message)
- client_socket.close() #: reset the connection.
- continue
-
- server_socket.connect(("127.0.0.1", webport))
-
- self.manager.startThread(forward, client_socket, server_socket)
- self.manager.startThread(forward, server_socket, client_socket)
-
- except socket.timeout:
- self.logDebug("Connection timed out, retrying...")
- return self._server(ip, webport, cnlport)
-
- except socket.error, e:
- self.logError(e)
- time.sleep(240)
- return self._server(ip, webport, cnlport)
diff --git a/module/plugins/hooks/DeathByCaptcha.py b/module/plugins/hooks/DeathByCaptcha.py
deleted file mode 100644
index f9618f011..000000000
--- a/module/plugins/hooks/DeathByCaptcha.py
+++ /dev/null
@@ -1,220 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import pycurl
-import re
-import time
-
-from base64 import b64encode
-
-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, threaded
-
-
-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.06"
-
- __config__ = [("username", "str", "Username", ""),
- ("passkey", "password", "Password", ""),
- ("force", "bool", "Force DBC even if client is connected", False)]
-
- __description__ = """Send captchas to DeathByCaptcha.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://api.dbcapi.me/api/"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def api_response(self, api="captcha", post=False, multipart=False):
- req = getRequest()
- req.c.setopt(pycurl.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')})
-
- res = None
- try:
- json = req.load("%s%s" % (self.API_URL, api),
- post=post,
- multipart=multipart)
- self.logDebug(json)
- res = json_loads(json)
-
- if "error" in res:
- raise DeathByCaptchaException(res['error'])
- elif "status" not in res:
- raise DeathByCaptchaException(str(res))
-
- 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 res
-
-
- def getCredits(self):
- res = self.api_response("user", True)
-
- if 'is_banned' in res and res['is_banned']:
- raise DeathByCaptchaException('banned')
- elif 'balance' in res and 'rate' in res:
- self.info.update(res)
- else:
- raise DeathByCaptchaException(res)
-
-
- def getStatus(self):
- res = self.api_response("status", False)
-
- if 'is_service_overloaded' in res and res['is_service_overloaded']:
- raise DeathByCaptchaException('service-overload')
-
-
- def submit(self, captcha, captchaType="file", match=None):
- #@NOTE: Workaround multipart-post bug in HTTPRequest.py
- if re.match("^\w*$", self.getConfig('passkey')):
- multipart = True
- data = (pycurl.FORM_FILE, captcha)
- else:
- multipart = False
- with open(captcha, 'rb') as f:
- data = f.read()
- data = "base64:" + b64encode(data)
-
- res = self.api_response("captcha", {"captchafile": data}, multipart)
-
- if "captcha" not in res:
- raise DeathByCaptchaException(res)
- ticket = res['captcha']
-
- for _i in xrange(24):
- time.sleep(5)
- res = self.api_response("captcha/%d" % ticket, False)
- if res['text'] and res['is_correct']:
- break
- else:
- raise DeathByCaptchaException('timed-out')
-
- result = res['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)
- self._processCaptcha(task)
-
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- try:
- res = self.api_response("captcha/%d/report" % task.data['ticket'], True)
-
- except DeathByCaptchaException, e:
- self.logError(e.getDesc())
-
- except Exception, e:
- self.logError(e)
-
-
- @threaded
- 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/DebridItaliaComHook.py b/module/plugins/hooks/DebridItaliaComHook.py
deleted file mode 100644
index 36b307696..000000000
--- a/module/plugins/hooks/DebridItaliaComHook.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class DebridItaliaComHook(MultiHook):
- __name__ = "DebridItaliaComHook"
- __type__ = "hook"
- __version__ = "0.12"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Debriditalia.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell" , "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- def getHosters(self):
- return self.getURL("http://debriditalia.com/api.php", get={'hosts': ""}).replace('"', '').split(',')
diff --git a/module/plugins/hooks/DeleteFinished.py b/module/plugins/hooks/DeleteFinished.py
deleted file mode 100644
index bde4ca259..000000000
--- a/module/plugins/hooks/DeleteFinished.py
+++ /dev/null
@@ -1,81 +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.12"
-
- __config__ = [("interval" , "int" , "Check interval in hours" , 72 ),
- ("deloffline", "bool", "Delete package with offline links", False)]
-
- __description__ = """Automatically delete all finished packages from queue"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- # event_list = ["pluginConfigChanged"]
-
- MIN_CHECK_INTERVAL = 1 * 60 * 60 #: 1 hour
-
-
- ## overwritten methods ##
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.interval = self.MIN_CHECK_INTERVAL
-
-
- 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.manager.removeEvent('packageFinished', self.wakeup)
-
-
- def coreReady(self):
- self.info['sleep'] = True
- # interval = self.getConfig('interval')
- # self.pluginConfigChanged(self.__name__, 'interval', interval)
- self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60 * 60)
- 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.manager.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.manager.events:
- if func in self.manager.events[event]:
- self.logDebug("Function already registered", func)
- else:
- self.manager.events[event].append(func)
- else:
- self.manager.events[event] = [func]
diff --git a/module/plugins/hooks/DownloadScheduler.py b/module/plugins/hooks/DownloadScheduler.py
deleted file mode 100644
index 9c74ac1d4..000000000
--- a/module/plugins/hooks/DownloadScheduler.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Hook import Hook
-
-
-class DownloadScheduler(Hook):
- __name__ = "DownloadScheduler"
- __type__ = "hook"
- __version__ = "0.22"
-
- __config__ = [("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"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- 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 = time.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/EasybytezComHook.py b/module/plugins/hooks/EasybytezComHook.py
deleted file mode 100644
index 43efb5048..000000000
--- a/module/plugins/hooks/EasybytezComHook.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class EasybytezComHook(MultiHook):
- __name__ = "EasybytezComHook"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """EasyBytez.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def getHosters(self):
- user, data = self.account.selectAccount()
-
- req = self.account.getAccountRequest(user)
- html = req.load("http://www.easybytez.com")
-
- return re.search(r'</textarea>\s*Supported sites:(.*)', html).group(1).split(',')
diff --git a/module/plugins/hooks/ExpertDecoders.py b/module/plugins/hooks/ExpertDecoders.py
deleted file mode 100644
index ccd48f664..000000000
--- a/module/plugins/hooks/ExpertDecoders.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import pycurl
-import uuid
-
-from base64 import b64encode
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-class ExpertDecoders(Hook):
- __name__ = "ExpertDecoders"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("force", "bool", "Force CT even if client is connected", False),
- ("passkey", "password", "Access key", "")]
-
- __description__ = """Send captchas to expertdecoders.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- API_URL = "http://www.fasttypers.org/imagepost.ashx"
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.API_URL, post={"key": self.getConfig('passkey'), "action": "balance"})
-
- if res.isdigit():
- self.logInfo(_("%s credits left") % res)
- self.info['credits'] = credits = int(res)
- return credits
- else:
- self.logError(res)
- return 0
-
-
- @threaded
- def _processCaptcha(self, task):
- task.data['ticket'] = ticket = uuid.uuid4()
- result = None
-
- with open(task.captchaFile, 'rb') as f:
- data = f.read()
-
- req = getRequest()
- #raise timeout threshold
- req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
-
- try:
- result = req.load(self.API_URL,
- post={'action' : "upload",
- 'key' : self.getConfig('passkey'),
- 'file' : b64encode(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)
- self._processCaptcha(task)
-
- else:
- self.logInfo(_("Your ExpertDecoders Account has not enough credits"))
-
-
- def captchaInvalid(self, task):
- if "ticket" in task.data:
-
- try:
- res = getURL(self.API_URL,
- post={'action': "refund", 'key': self.getConfig('passkey'), 'gen_task_id': task.data['ticket']})
- self.logInfo(_("Request refund"), res)
-
- except BadHeader, e:
- self.logError(_("Could not send refund request"), e)
diff --git a/module/plugins/hooks/ExternalScripts.py b/module/plugins/hooks/ExternalScripts.py
deleted file mode 100644
index be0857009..000000000
--- a/module/plugins/hooks/ExternalScripts.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import subprocess
-
-from module.plugins.Hook import Hook
-from module.utils import fs_encode, save_join
-
-
-class ExternalScripts(Hook):
- __name__ = "ExternalScripts"
- __type__ = "hook"
- __version__ = "0.39"
-
- __config__ = [("activated", "bool", "Activated" , True ),
- ("waitend" , "bool", "Wait script ending", False)]
-
- __description__ = """Run external scripts"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay" , "mkaay@mkaay.de" ),
- ("RaNaN" , "ranan@pyload.org" ),
- ("spoob" , "spoob@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- event_list = ["archive_extract_failed", "archive_extracted" ,
- "package_extract_failed", "package_extracted" ,
- "all_archives_extracted", "all_archives_processed",
- "allDownloadsFinished" , "allDownloadsProcessed" ,
- "packageDeleted"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {'oldip': None}
- self.scripts = {}
-
- folders = ["pyload_start", "pyload_restart", "pyload_stop",
- "before_reconnect", "after_reconnect",
- "download_preparing", "download_failed", "download_finished",
- "archive_extract_failed", "archive_extracted",
- "package_finished", "package_deleted", "package_extract_failed", "package_extracted",
- "all_downloads_processed", "all_downloads_finished", #@TODO: Invert `all_downloads_processed`, `all_downloads_finished` order in 0.4.10
- "all_archives_extracted", "all_archives_processed"]
-
- for folder in folders:
- self.scripts[folder] = []
- for dir in (pypath, ''):
- self.initPluginType(folder, os.path.join(dir, 'scripts', folder))
-
- for script_type, names in self.scripts.iteritems():
- if names:
- self.logInfo(_("Installed scripts for: ") + script_type, ", ".join(map(os.path.basename, names)))
-
- self.pyload_start()
-
-
- def initPluginType(self, name, dir):
- if not os.path.isdir(dir):
- try:
- os.makedirs(dir)
-
- except OSError, e:
- self.logDebug(e)
- return
-
- for filename in os.listdir(dir):
- file = save_join(dir, filename)
-
- if not os.path.isfile(file):
- continue
-
- if filename[0] in ("#", "_") or filename.endswith("~") or filename.endswith(".swp"):
- continue
-
- if not os.access(file, os.X_OK):
- self.logWarning(_("Script not executable:") + " %s/%s" % (name, filename))
-
- self.scripts[name].append(file)
-
-
- def callScript(self, script, *args):
- try:
- cmd_args = [fs_encode(str(x) if not isinstance(x, basestring) else x) for x in args]
- cmd = [script] + cmd_args
-
- self.logDebug("Executing: %s" % os.path.abspath(script), "Args: " + ' '.join(cmd_args))
-
- p = subprocess.Popen(cmd, bufsize=-1) #@NOTE: output goes to pyload
- if self.getConfig('waitend'):
- p.communicate()
-
- except Exception, e:
- try:
- self.logError(_("Runtime error: %s") % os.path.abspath(script), e)
- except Exception:
- self.logError(_("Runtime error: %s") % os.path.abspath(script), _("Unknown error"))
-
-
- def pyload_start(self):
- for script in self.scripts['pyload_start']:
- self.callScript(script)
-
-
- def coreExiting(self):
- for script in self.scripts['pyload_restart' if self.core.do_restart else 'pyload_stop']:
- self.callScript(script)
-
-
- def beforeReconnecting(self, ip):
- for script in self.scripts['before_reconnect']:
- self.callScript(script, ip)
- self.info['oldip'] = ip
-
-
- def afterReconnecting(self, ip):
- for script in self.scripts['after_reconnect']:
- self.callScript(script, ip, self.info['oldip']) #@TODO: Use built-in oldip in 0.4.10
-
-
- def downloadPreparing(self, pyfile):
- for script in self.scripts['download_preparing']:
- self.callScript(script, pyfile.id, pyfile.name, None, pyfile.pluginname, pyfile.url)
-
-
- def downloadFailed(self, pyfile):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pyfile.package().folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['download_failed']:
- file = save_join(download_folder, pyfile.name)
- self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
-
-
- def downloadFinished(self, pyfile):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pyfile.package().folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['download_finished']:
- file = save_join(download_folder, pyfile.name)
- self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
-
-
- def archive_extract_failed(self, pyfile, archive):
- for script in self.scripts['archive_extract_failed']:
- self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
-
-
- def archive_extracted(self, pyfile, archive):
- for script in self.scripts['archive_extracted']:
- self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
-
-
- def packageFinished(self, pypack):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_finished']:
- self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
-
-
- def packageDeleted(self, pid):
- pack = self.core.api.getPackageInfo(pid)
-
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_deleted']:
- self.callScript(script, pack.id, pack.name, download_folder, pack.password)
-
-
- def package_extract_failed(self, pypack):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_extract_failed']:
- self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
-
-
- def package_extracted(self, pypack):
- if self.config['general']['folder_per_package']:
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder)
- else:
- download_folder = self.config['general']['download_folder']
-
- for script in self.scripts['package_extracted']:
- self.callScript(script, pypack.id, pypack.name, download_folder)
-
-
- def allDownloadsFinished(self):
- for script in self.scripts['all_downloads_finished']:
- self.callScript(script)
-
-
- def allDownloadsProcessed(self):
- for script in self.scripts['all_downloads_processed']:
- self.callScript(script)
-
-
- def all_archives_extracted(self):
- for script in self.scripts['all_archives_extracted']:
- self.callScript(script)
-
-
- def all_archives_processed(self):
- for script in self.scripts['all_archives_processed']:
- self.callScript(script)
diff --git a/module/plugins/hooks/ExtractArchive.py b/module/plugins/hooks/ExtractArchive.py
deleted file mode 100644
index 5b9e3f30b..000000000
--- a/module/plugins/hooks/ExtractArchive.py
+++ /dev/null
@@ -1,571 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import sys
-import traceback
-
-# 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
- import subprocess
-
- 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
-
- subprocess.Popen.wait = wait
-
-try:
- import send2trash
-except ImportError:
- pass
-
-from copy import copy
-if os.name != "nt":
- from grp import getgrnam
- from pwd import getpwnam
-
-from module.plugins.Hook import Hook, Expose, threaded
-from module.plugins.internal.Extractor import ArchiveError, CRCError, PasswordError
-from module.plugins.internal.SimpleHoster import replace_patterns
-from module.utils import fs_encode, save_join, uniqify
-
-
-class ArchiveQueue(object):
-
- def __init__(self, plugin, storage):
- self.plugin = plugin
- self.storage = storage
-
-
- def get(self):
- try:
- return [int(pid) for pid in self.plugin.getStorage("ExtractArchive:%s" % self.storage, "").decode('base64').split()]
- except Exception:
- return []
-
-
- def set(self, value):
- if isinstance(value, list):
- item = str(value)[1:-1].replace(' ', '').replace(',', ' ')
- else:
- item = str(value).strip()
- return self.plugin.setStorage("ExtractArchive:%s" % self.storage, item.encode('base64')[:-1])
-
-
- def delete(self):
- return self.plugin.delStorage("ExtractArchive:%s" % self.storage)
-
-
- def add(self, item):
- queue = self.get()
- if item not in queue:
- return self.set(queue + [item])
- else:
- return True
-
-
- def remove(self, item):
- queue = self.get()
- try:
- queue.remove(item)
-
- except ValueError:
- pass
-
- if queue == []:
- return self.delete()
-
- return self.set(queue)
-
-
-class ExtractArchive(Hook):
- __name__ = "ExtractArchive"
- __type__ = "hook"
- __version__ = "1.44"
-
- __config__ = [("activated" , "bool" , "Activated" , True ),
- ("fullpath" , "bool" , "Extract with full paths" , True ),
- ("overwrite" , "bool" , "Overwrite files" , False ),
- ("keepbroken" , "bool" , "Try to extract broken archives" , False ),
- ("repair" , "bool" , "Repair broken archives (RAR required)" , False ),
- ("test" , "bool" , "Test archive before extracting" , False ),
- ("usepasswordfile", "bool" , "Use password file" , True ),
- ("passwordfile" , "file" , "Password file" , "archive_password.txt" ),
- ("delete" , "bool" , "Delete archive after extraction" , True ),
- ("deltotrash" , "bool" , "Move to trash (recycle bin) instead delete", True ),
- ("subfolder" , "bool" , "Create subfolder for each package" , False ),
- ("destination" , "folder" , "Extract files to folder" , "" ),
- ("extensions" , "str" , "Extract archives ending with extension" , "7z,bz2,bzip2,gz,gzip,lha,lzh,lzma,rar,tar,taz,tbz,tbz2,tgz,xar,xz,z,zip"),
- ("excludefiles" , "str" , "Don't extract the following files" , "*.nfo,*.DS_Store,index.dat,thumb.db" ),
- ("recursive" , "bool" , "Extract archives in archives" , True ),
- ("waitall" , "bool" , "Run after all downloads was processed" , False ),
- ("renice" , "int" , "CPU priority" , 0 )]
-
- __description__ = """Extract different kind of archives"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("Immenz" , "immenz@gmx.net" )]
-
-
- event_list = ["allDownloadsProcessed","packageDeleted"]
-
- NAME_REPLACEMENTS = [(r'\.part\d+\.rar$', ".part.rar")]
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
- self.queue = ArchiveQueue(self, "Queue")
- self.failed = ArchiveQueue(self, "Failed")
-
- self.interval = 60
- self.extracting = False
- self.lastPackage = False
- self.extractors = []
- self.passwords = []
- self.repair = False
-
-
- def coreReady(self):
- for p in ("UnRar", "SevenZip", "UnZip"):
- try:
- module = self.core.pluginManager.loadModule("internal", p)
- klass = getattr(module, p)
- if klass.isUsable():
- self.extractors.append(klass)
- if klass.REPAIR:
- self.repair = self.getConfig('repair')
-
- except OSError, e:
- if e.errno == 2:
- self.logWarning(_("No %s installed") % p)
- else:
- self.logWarning(_("Could not activate: %s") % p, e)
- if self.core.debug:
- traceback.print_exc()
-
- except Exception, e:
- self.logWarning(_("Could not activate: %s") % p, e)
- if self.core.debug:
- traceback.print_exc()
-
- if self.extractors:
- self.logDebug(*["Found %s %s" % (Extractor.__name__, Extractor.VERSION) for Extractor in self.extractors])
- self.extractQueued() #: Resume unfinished extractions
- else:
- self.logInfo(_("No Extract plugins activated"))
-
-
- @threaded
- def extractQueued(self, thread):
- if self.extracting: #@NOTE: doing the check here for safty (called by coreReady)
- return
-
- self.extracting = True
-
- packages = self.queue.get()
- while packages:
- if self.lastPackage: #: called from allDownloadsProcessed
- self.lastPackage = False
- if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
- self.manager.dispatchEvent("all_archives_extracted")
- self.manager.dispatchEvent("all_archives_processed")
- else:
- if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
- pass
-
- packages = self.queue.get() #: check for packages added during extraction
-
- self.extracting = False
-
-
- @Expose
- def extractPackage(self, *ids):
- """ Extract packages with given id"""
- for id in ids:
- self.queue.add(id)
- if not self.getConfig('waitall') and not self.extracting:
- self.extractQueued()
-
-
- def packageDeleted(self, pid):
- self.queue.remove(pid)
-
-
- def packageFinished(self, pypack):
- self.queue.add(pypack.id)
- if not self.getConfig('waitall') and not self.extracting:
- self.extractQueued()
-
-
- def allDownloadsProcessed(self):
- self.lastPackage = True
- if self.getConfig('waitall') and not self.extracting:
- self.extractQueued()
-
-
- @Expose
- def extract(self, ids, thread=None): #@TODO: Use pypack, not pid to improve method usability
- if not ids:
- return False
-
- processed = []
- extracted = []
- failed = []
-
- toList = lambda string: string.replace(' ', '').replace(',', '|').replace(';', '|').split('|')
-
- destination = self.getConfig('destination')
- subfolder = self.getConfig('subfolder')
- fullpath = self.getConfig('fullpath')
- overwrite = self.getConfig('overwrite')
- renice = self.getConfig('renice')
- recursive = self.getConfig('recursive')
- delete = self.getConfig('delete')
- keepbroken = self.getConfig('keepbroken')
-
- extensions = [x.lstrip('.').lower() for x in toList(self.getConfig('extensions'))]
- excludefiles = toList(self.getConfig('excludefiles'))
-
- if extensions:
- self.logDebug("Use for extensions: %s" % "|.".join(extensions))
-
- # reload from txt file
- self.reloadPasswords()
-
- download_folder = self.config['general']['download_folder']
-
- # iterate packages -> extractors -> targets
- for pid in ids:
- pypack = self.core.files.getPackage(pid)
-
- if not pypack:
- self.queue.remove(pid)
- continue
-
- self.logInfo(_("Check package: %s") % pypack.name)
-
- # determine output folder
- out = save_join(download_folder, pypack.folder, destination, "") #: force trailing slash
-
- if subfolder:
- out = save_join(out, pypack.folder)
-
- if not os.path.exists(out):
- os.makedirs(out)
-
- matched = False
- success = True
- files_ids = dict((pylink['name'],((save_join(download_folder, pypack.folder, pylink['name'])), pylink['id'], out)) for pylink \
- in sorted(pypack.getChildren().itervalues(), key=lambda k: k['name'])).values() #: remove duplicates
-
- # check as long there are unseen files
- while files_ids:
- new_files_ids = []
-
- if extensions:
- files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids \
- if filter(lambda ext: fname.lower().endswith(ext), extensions)]
-
- for Extractor in self.extractors:
- targets = Extractor.getTargets(files_ids)
- if targets:
- self.logDebug("Targets for %s: %s" % (Extractor.__name__, targets))
- matched = True
-
- for fname, fid, fout in targets:
- name = os.path.basename(fname)
-
- if not os.path.exists(fname):
- self.logDebug(name, "File not found")
- continue
-
- self.logInfo(name, _("Extract to: %s") % fout)
- try:
- pyfile = self.core.files.getFile(fid)
- archive = Extractor(self,
- fname,
- fout,
- fullpath,
- overwrite,
- excludefiles,
- renice,
- delete,
- keepbroken,
- fid)
-
- thread.addActive(pyfile)
- archive.init()
-
- try:
- new_files = self._extract(pyfile, archive, pypack.password)
-
- finally:
- pyfile.setProgress(100)
- thread.finishFile(pyfile)
-
- except Exception, e:
- self.logError(name, e)
- success = False
- continue
-
- # remove processed file and related multiparts from list
- files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids \
- if fname not in archive.getDeleteFiles()]
- self.logDebug("Extracted files: %s" % new_files)
- self.setPermissions(new_files)
-
- for filename in new_files:
- file = fs_encode(save_join(os.path.dirname(archive.filename), filename))
- if not os.path.exists(file):
- self.logDebug("New file %s does not exists" % filename)
- continue
-
- if recursive and os.path.isfile(file):
- new_files_ids.append((filename, fid, os.path.dirname(filename))) #: append as new target
-
- self.manager.dispatchEvent("archive_extracted", pyfile, archive)
-
- files_ids = new_files_ids #: also check extracted files
-
- if matched:
- if success:
- extracted.append(pid)
- self.manager.dispatchEvent("package_extracted", pypack)
-
- else:
- failed.append(pid)
- self.manager.dispatchEvent("package_extract_failed", pypack)
-
- self.failed.add(pid)
- else:
- self.logInfo(_("No files found to extract"))
-
- if not matched or not success and subfolder:
- try:
- os.rmdir(out)
-
- except OSError:
- pass
-
- self.queue.remove(pid)
-
- return True if not failed else False
-
-
- def _extract(self, pyfile, archive, password):
- name = os.path.basename(archive.filename)
-
- pyfile.setStatus("processing")
-
- encrypted = False
- try:
- self.logDebug("Password: %s" % (password or "None provided"))
- passwords = uniqify([password] + self.getPasswords(False)) if self.getConfig('usepasswordfile') else [password]
- for pw in passwords:
- try:
- if self.getConfig('test') or self.repair:
- pyfile.setCustomStatus(_("archive testing"))
- if pw:
- self.logDebug("Testing with password: %s" % pw)
- pyfile.setProgress(0)
- archive.verify(pw)
- pyfile.setProgress(100)
- else:
- archive.check(pw)
-
- self.addPassword(pw)
- break
-
- except PasswordError:
- if not encrypted:
- self.logInfo(name, _("Password protected"))
- encrypted = True
-
- except CRCError, e:
- self.logDebug(name, e)
- self.logInfo(name, _("CRC Error"))
-
- if self.repair:
- self.logWarning(name, _("Repairing..."))
-
- pyfile.setCustomStatus(_("archive repairing"))
- pyfile.setProgress(0)
- repaired = archive.repair()
- pyfile.setProgress(100)
-
- if not repaired and not self.getConfig('keepbroken'):
- raise CRCError("Archive damaged")
-
- self.addPassword(pw)
- break
-
- raise CRCError("Archive damaged")
-
- except ArchiveError, e:
- raise ArchiveError(e)
-
- pyfile.setCustomStatus(_("extracting"))
- pyfile.setProgress(0)
-
- if not encrypted or not self.getConfig('usepasswordfile'):
- self.logDebug("Extracting using password: %s" % (password or "None"))
- archive.extract(password)
- else:
- for pw in filter(None, uniqify([password] + self.getPasswords(False))):
- try:
- self.logDebug("Extracting using password: %s" % pw)
-
- archive.extract(pw)
- self.addPassword(pw)
- break
-
- except PasswordError:
- self.logDebug("Password was wrong")
- else:
- raise PasswordError
-
- pyfile.setProgress(100)
- pyfile.setStatus("processing")
-
- delfiles = archive.getDeleteFiles()
- self.logDebug("Would delete: " + ", ".join(delfiles))
-
- if self.getConfig('delete'):
- self.logInfo(_("Deleting %s files") % len(delfiles))
-
- deltotrash = self.getConfig('deltotrash')
- for f in delfiles:
- file = fs_encode(f)
- if not os.path.exists(file):
- continue
-
- if not deltotrash:
- os.remove(file)
-
- else:
- try:
- send2trash.send2trash(file)
-
- except NameError:
- self.logWarning(_("Unable to move %s to trash: Send2Trash lib not found") % os.path.basename(f))
-
- except Exception, e:
- self.logWarning(_("Unable to move %s to trash: %s") % (os.path.basename(f), e.message))
-
- else:
- self.logDebug(_("Successfully moved %s to trash") % os.path.basename(f))
-
- self.logInfo(name, _("Extracting finished"))
- extracted_files = archive.files or archive.list()
-
- return extracted_files
-
- except PasswordError:
- self.logError(name, _("Wrong password" if password else "No password found"))
-
- except CRCError, e:
- self.logError(name, _("CRC mismatch"), e)
-
- except ArchiveError, e:
- self.logError(name, _("Archive error"), e)
-
- except Exception, e:
- self.logError(name, _("Unknown error"), e)
- if self.core.debug:
- traceback.print_exc()
-
- self.manager.dispatchEvent("archive_extract_failed", pyfile, archive)
-
- raise Exception(_("Extract failed"))
-
-
- @Expose
- def getPasswords(self, reload=True):
- """ List of saved passwords """
- if reload:
- self.reloadPasswords()
-
- return self.passwords
-
-
- def reloadPasswords(self):
- try:
- passwords = []
-
- file = fs_encode(self.getConfig('passwordfile'))
- with open(file) as f:
- for pw in f.read().splitlines():
- passwords.append(pw)
-
- except IOError, e:
- self.logError(e)
-
- else:
- self.passwords = passwords
-
-
- @Expose
- def addPassword(self, password):
- """ Adds a password to saved list"""
- try:
- self.passwords = uniqify([password] + self.passwords)
-
- file = fs_encode(self.getConfig('passwordfile'))
- with open(file, "wb") as f:
- for pw in self.passwords:
- f.write(pw + '\n')
-
- except IOError, e:
- self.logError(e)
-
-
- def setPermissions(self, files):
- for f in files:
- if not os.path.exists(f):
- continue
-
- try:
- if self.config['permission']['change_file']:
- if os.path.isfile(f):
- os.chmod(f, int(self.config['permission']['file'], 8))
-
- elif os.path.isdir(f):
- os.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]
- os.chown(f, uid, gid)
-
- except Exception, e:
- self.logWarning(_("Setting User and Group failed"), e)
diff --git a/module/plugins/hooks/FastixRuHook.py b/module/plugins/hooks/FastixRuHook.py
deleted file mode 100644
index 16e30e93a..000000000
--- a/module/plugins/hooks/FastixRuHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class FastixRuHook(MultiHook):
- __name__ = "FastixRuHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Fastix.ru hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
-
-
- def getHosters(self):
- html = self.getURL("http://fastix.ru/api_v2",
- get={'apikey': "5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y",
- 'sub' : "allowed_sources"})
- host_list = json_loads(html)
- host_list = host_list['allow']
- return host_list
diff --git a/module/plugins/hooks/FreeWayMeHook.py b/module/plugins/hooks/FreeWayMeHook.py
deleted file mode 100644
index baea44540..000000000
--- a/module/plugins/hooks/FreeWayMeHook.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class FreeWayMeHook(MultiHook):
- __name__ = "FreeWayMeHook"
- __type__ = "hook"
- __version__ = "0.15"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """FreeWay.me hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Nicolas Giese", "james@free-way.me")]
-
-
- def getHosters(self):
- # Get account data
- if not self.account or not self.account.canUse():
- hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3}).replace("\"", "").strip()
- else:
- self.logDebug("AccountInfo available - Get HosterList with User Pass")
- (user, data) = self.account.selectAccount()
- hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3, "user": user, "pass": data['password']}).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 f771cf232..000000000
--- a/module/plugins/hooks/HotFolder.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import time
-
-from shutil import move
-
-from module.plugins.Hook import Hook
-from module.utils import fs_encode, save_join
-
-
-class HotFolder(Hook):
- __name__ = "HotFolder"
- __type__ = "hook"
- __version__ = "0.14"
-
- __config__ = [("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"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.de")]
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.interval = 30
-
-
- def periodical(self):
- folder = fs_encode(self.getConfig('folder'))
- file = fs_encode(self.getConfig('file'))
-
- try:
- if not os.path.isdir(os.path.join(folder, "finished")):
- os.makedirs(os.path.join(folder, "finished"))
-
- if self.getConfig('watch_file'):
- with open(file, "a+") as f:
- f.seek(0)
- content = f.read().strip()
-
- if content:
- f = open(file, "wb")
- f.close()
-
- name = "%s_%s.txt" % (file, time.strftime("%H-%M-%S_%d%b%Y"))
-
- with open(save_join(folder, "finished", name), "wb") as f:
- f.write(content)
-
- self.core.api.addPackage(f.name, [f.name], 1)
-
- for f in os.listdir(folder):
- path = os.path.join(folder, f)
-
- if not os.path.isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."):
- continue
-
- newpath = os.path.join(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)
-
- except (IOError, OSError), e:
- self.logError(e)
diff --git a/module/plugins/hooks/IRCInterface.py b/module/plugins/hooks/IRCInterface.py
deleted file mode 100644
index 33fde3d20..000000000
--- a/module/plugins/hooks/IRCInterface.py
+++ /dev/null
@@ -1,433 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import socket
-import ssl
-import time
-import traceback
-
-from select import select
-from threading import Thread
-
-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.13"
-
- __config__ = [("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"),
- ("ssl", "bool", "Use SSL", False),
- ("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"""
- __license__ = "GPLv3"
- __authors__ = [("Jeix", "Jeix@hasnomail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def __init__(self, core, manager):
- Thread.__init__(self)
- Hook.__init__(self, core, manager)
- self.setDaemon(True)
-
-
- 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 Exception:
- 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 Exception:
- pass
-
-
- def newCaptchaTask(self, task):
- if self.getConfig('captcha') and task.isTextual():
- task.handler.append(self)
- task.setWaiting(60)
-
- html = getURL("http://www.freeimagehosting.net/upload.php",
- post={"attached": (pycurl.FORM_FILE, task.captchaFile)}, multipart=True)
-
- url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", html).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')))
-
- if self.getConfig('ssl'):
- self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE) #@TODO: support certificate
-
- 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(_("Connected to"), host)
- self.logInfo(_("Switching to listening mode!"))
- try:
- self.main_loop()
-
- except IRCError, ex:
- self.sock.send("QUIT :byebye\r\n")
- traceback.print_exc()
- self.sock.close()
-
-
- def main_loop(self):
- readbuffer = ""
- while True:
- time.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 Exception:
- 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(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.core.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.core.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.core.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.core.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.core.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.core.api.unpauseServer()
- return ["INFO: Starting downloads."]
-
-
- def event_stop(self, args):
- self.core.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.core.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 Exception:
- # create new package
- id = self.core.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.core.api.deletePackages(map(int, args[1:]))
- return ["INFO: Deleted %d packages!" % len(args[1:])]
-
- elif args[0] == "-l":
- ret = self.core.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.core.api.getPackageInfo(id)
- except PackageDoesNotExists:
- return ["ERROR: Package #%d does not exist." % id]
-
- self.core.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.core.api.getPackageData(id):
- return ["ERROR: Package #%d does not exist." % id]
-
- self.core.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 f1fcacb71..000000000
--- a/module/plugins/hooks/ImageTyperz.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import pycurl
-import re
-
-from base64 import b64encode
-
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook, threaded
-
-
-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.06"
-
- __config__ = [("username", "str", "Username", ""),
- ("passkey", "password", "Password", ""),
- ("force", "bool", "Force IT even if client is connected", False)]
-
- __description__ = """Send captchas to ImageTyperz.com"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- 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 = {} #@TODO: Remove in 0.4.10
-
-
- def getCredits(self):
- res = getURL(self.GETCREDITS_URL,
- post={'action': "REQUESTBALANCE",
- 'username': self.getConfig('username'),
- 'password': self.getConfig('passkey')})
-
- if res.startswith('ERROR'):
- raise ImageTyperzException(res)
-
- try:
- balance = float(res)
- except Exception:
- raise ImageTyperzException("Invalid response")
-
- self.logInfo(_("Account balance: $%s left") % res)
- return balance
-
-
- def submit(self, captcha, captchaType="file", match=None):
- req = getRequest()
- #raise timeout threshold
- req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
-
- try:
- #@NOTE: Workaround multipart-post bug in HTTPRequest.py
- if re.match("^\w*$", self.getConfig('passkey')):
- multipart = True
- data = (pycurl.FORM_FILE, captcha)
- else:
- multipart = False
- with open(captcha, 'rb') as f:
- data = f.read()
- data = b64encode(data)
-
- res = req.load(self.SUBMIT_URL,
- post={'action': "UPLOADCAPTCHA",
- 'username': self.getConfig('username'),
- 'password': self.getConfig('passkey'), "file": data},
- multipart=multipart)
- finally:
- req.close()
-
- if res.startswith("ERROR"):
- raise ImageTyperzException(res)
- else:
- data = res.split('|')
- if len(data) == 2:
- ticket, result = data
- else:
- raise ImageTyperzException("Unknown response: %s" % res)
-
- 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)
- 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:
- res = getURL(self.RESPOND_URL,
- post={'action': "SETBADIMAGE",
- 'username': self.getConfig('username'),
- 'password': self.getConfig('passkey'),
- 'imageid': task.data['ticket']})
-
- if res == "SUCCESS":
- self.logInfo(_("Bad captcha solution received, requested refund"))
- else:
- self.logError(_("Bad captcha solution received, refund request failed"), res)
-
-
- @threaded
- 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/JustPremium.py b/module/plugins/hooks/JustPremium.py
deleted file mode 100644
index d18afc524..000000000
--- a/module/plugins/hooks/JustPremium.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hook import Hook
-
-
-class JustPremium(Hook):
- __name__ = "JustPremium"
- __type__ = "hook"
- __version__ = "0.22"
-
- __config__ = [("excluded", "str", "Exclude hosters (comma separated)", ""),
- ("included", "str", "Include hosters (comma separated)", "")]
-
- __description__ = """Remove not-premium links from added urls"""
- __license__ = "GPLv3"
- __authors__ = [("mazleu" , "mazleica@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com" ),
- ("immenz" , "immenz@gmx.net" )]
-
-
- event_list = ["linksAdded"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def linksAdded(self, links, pid):
- hosterdict = self.core.pluginManager.hosterPlugins
- linkdict = self.core.api.checkURLs(links)
-
- premiumplugins = set(account.type for account in self.core.api.getAccounts(False) \
- if account.valid and account.premium)
- multihosters = set(hoster for hoster in self.core.pluginManager.hosterPlugins \
- if 'new_name' in hosterdict[hoster] \
- and hosterdict[hoster]['new_name'] in premiumplugins)
-
- excluded = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
- self.getConfig('excluded').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
- included = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
- self.getConfig('included').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
-
- hosterlist = (premiumplugins | multihosters).union(excluded).difference(included)
-
- #: Found at least one hoster with account or multihoster
- if not any( True for pluginname in linkdict if pluginname in hosterlist ):
- return
-
- for pluginname in set(linkdict.keys()) - hosterlist:
- self.logInfo(_("Remove links of plugin: %s") % pluginname)
- for link in linkdict[pluginname]:
- self.logDebug("Remove link: %s" % link)
- links.remove(link)
diff --git a/module/plugins/hooks/LinkdecrypterComHook.py b/module/plugins/hooks/LinkdecrypterComHook.py
deleted file mode 100644
index b2eaece62..000000000
--- a/module/plugins/hooks/LinkdecrypterComHook.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class LinkdecrypterComHook(MultiHook):
- __name__ = "LinkdecrypterComHook"
- __type__ = "hook"
- __version__ = "1.06"
-
- __config__ = [("activated" , "bool" , "Activated" , True ),
- ("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Linkdecrypter.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def getHosters(self):
- list = re.search(r'>Supported\(\d+\)</b>: <i>(.[\w.\-, ]+)',
- self.getURL("http://linkdecrypter.com/", decode=True).replace("(g)", "")).group(1).split(', ')
- try:
- list.remove("download.serienjunkies.org")
- except ValueError:
- pass
-
- return list
diff --git a/module/plugins/hooks/LinksnappyComHook.py b/module/plugins/hooks/LinksnappyComHook.py
deleted file mode 100644
index 72282575b..000000000
--- a/module/plugins/hooks/LinksnappyComHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class LinksnappyComHook(MultiHook):
- __name__ = "LinksnappyComHook"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Linksnappy.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def getHosters(self):
- json_data = self.getURL("http://gen.linksnappy.com/lseAPI.php", get={'act': "FILEHOSTS"})
- json_data = json_loads(json_data)
-
- return json_data['return'].keys()
diff --git a/module/plugins/hooks/MegaDebridEuHook.py b/module/plugins/hooks/MegaDebridEuHook.py
deleted file mode 100644
index 0de7b4dcf..000000000
--- a/module/plugins/hooks/MegaDebridEuHook.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MegaDebridEuHook(MultiHook):
- __name__ = "MegaDebridEuHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Mega-debrid.eu hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("D.Ducatel", "dducatel@je-geek.fr")]
-
-
- def getHosters(self):
- reponse = self.getURL("http://www.mega-debrid.eu/api.php", get={'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/MegaRapidoNetHook.py b/module/plugins/hooks/MegaRapidoNetHook.py
deleted file mode 100644
index e113b305e..000000000
--- a/module/plugins/hooks/MegaRapidoNetHook.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MegaRapidoNetHook(MultiHook):
- __name__ = "MegaRapidoNetHook"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """MegaRapido.net hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def getHosters(self):
- hosters = {'1fichier' : [],#leave it there are so many possible addresses?
- '1st-files' : ['1st-files.com'],
- '2shared' : ['2shared.com'],
- '4shared' : ['4shared.com', '4shared-china.com'],
- 'asfile' : ['http://asfile.com/'],
- 'bitshare' : ['bitshare.com'],
- 'brupload' : ['brupload.net'],
- 'crocko' : ['crocko.com','easy-share.com'],
- 'dailymotion' : ['dailymotion.com'],
- 'depfile' : ['depfile.com'],
- 'depositfiles': ['depositfiles.com', 'dfiles.eu'],
- 'dizzcloud' : ['dizzcloud.com'],
- 'dl.dropbox' : [],
- 'extabit' : ['extabit.com'],
- 'extmatrix' : ['extmatrix.com'],
- 'facebook' : [],
- 'file4go' : ['file4go.com'],
- 'filecloud' : ['filecloud.io','ifile.it','mihd.net'],
- 'filefactory' : ['filefactory.com'],
- 'fileom' : ['fileom.com'],
- 'fileparadox' : ['fileparadox.in'],
- 'filepost' : ['filepost.com', 'fp.io'],
- 'filerio' : ['filerio.in','filerio.com','filekeen.com'],
- 'filesflash' : ['filesflash.com'],
- 'firedrive' : ['firedrive.com', 'putlocker.com'],
- 'flashx' : [],
- 'freakshare' : ['freakshare.net', 'freakshare.com'],
- 'gigasize' : ['gigasize.com'],
- 'hipfile' : ['hipfile.com'],
- 'junocloud' : ['junocloud.me'],
- 'letitbit' : ['letitbit.net','shareflare.net'],
- 'mediafire' : ['mediafire.com'],
- 'mega' : ['mega.co.nz'],
- 'megashares' : ['megashares.com'],
- 'metacafe' : ['metacafe.com'],
- 'netload' : ['netload.in'],
- 'oboom' : ['oboom.com'],
- 'rapidgator' : ['rapidgator.net'],
- 'rapidshare' : ['rapidshare.com'],
- 'rarefile' : ['rarefile.net'],
- 'ryushare' : ['ryushare.com'],
- 'sendspace' : ['sendspace.com'],
- 'turbobit' : ['turbobit.net', 'unextfiles.com'],
- 'uploadable' : ['uploadable.ch'],
- 'uploadbaz' : ['uploadbaz.com'],
- 'uploaded' : ['uploaded.to', 'uploaded.net', 'ul.to'],
- 'uploadhero' : ['uploadhero.com'],
- 'uploading' : ['uploading.com'],
- 'uptobox' : ['uptobox.com'],
- 'xvideos' : ['xvideos.com'],
- 'youtube' : ['youtube.com']}
-
- hoster_list = []
-
- for item in hosters.itervalues():
- hoster_list.extend(item)
-
- return hoster_list
diff --git a/module/plugins/hooks/MergeFiles.py b/module/plugins/hooks/MergeFiles.py
deleted file mode 100644
index 941938920..000000000
--- a/module/plugins/hooks/MergeFiles.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import re
-import traceback
-
-from module.plugins.Hook import Hook, threaded
-from module.utils import save_join
-
-
-class MergeFiles(Hook):
- __name__ = "MergeFiles"
- __type__ = "hook"
- __version__ = "0.14"
-
- __config__ = [("activated", "bool", "Activated", True)]
-
- __description__ = """Merges parts splitted with hjsplit"""
- __license__ = "GPLv3"
- __authors__ = [("and9000", "me@has-no-mail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
- BUFFER_SIZE = 4096
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- @threaded
- def packageFinished(self, pack):
- files = {}
- fid_dict = {}
- for fid, data in pack.getChildren().iteritems():
- if re.search("\.\d{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"), name)
-
- with open(save_join(download_folder, name), "wb") as final_file:
- for splitted_file in file_list:
- self.logDebug("Merging part", splitted_file)
-
- pyfile = self.core.files.getFile(fid_dict[splitted_file])
-
- pyfile.setStatus("processing")
-
- try:
- with open(save_join(download_folder, splitted_file), "rb") as s_file:
- 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
- self.logDebug("Finished merging part", splitted_file)
-
- except Exception, e:
- traceback.print_exc()
-
- finally:
- pyfile.setProgress(100)
- pyfile.setStatus("finished")
- pyfile.release()
-
- self.logInfo(_("Finished merging of"), name)
diff --git a/module/plugins/hooks/MultiHome.py b/module/plugins/hooks/MultiHome.py
deleted file mode 100644
index a26d139c0..000000000
--- a/module/plugins/hooks/MultiHome.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.plugins.Hook import Hook
-
-
-class MultiHome(Hook):
- __name__ = "MultiHome"
- __type__ = "hook"
- __version__ = "0.12"
-
- __config__ = [("interfaces", "str", "Interfaces", "None")]
-
- __description__ = """Ip address changer"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- 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("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.time()
-
-
- def __repr__(self):
- return "<Interface - %s>" % self.adress
diff --git a/module/plugins/hooks/MultihostersComHook.py b/module/plugins/hooks/MultihostersComHook.py
deleted file mode 100644
index 7b5e49c49..000000000
--- a/module/plugins/hooks/MultihostersComHook.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hooks.ZeveraComHook import ZeveraComHook
-
-
-class MultihostersComHook(ZeveraComHook):
- __name__ = "MultihostersComHook"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed", "bool" , "Revert to standard download if download fails", False),
- ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
-
- __description__ = """Multihosters.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("tjeh", "tjeh@gmx.net")]
diff --git a/module/plugins/hooks/MultishareCzHook.py b/module/plugins/hooks/MultishareCzHook.py
deleted file mode 100644
index 6052b7673..000000000
--- a/module/plugins/hooks/MultishareCzHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MultishareCzHook(MultiHook):
- __name__ = "MultishareCzHook"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """MultiShare.cz hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="(.+?)"></td>\s*<td class="stav">[^>]*?alt="OK"'
-
-
- def getHosters(self):
- html = self.getURL("http://www.multishare.cz/monitoring/")
- return re.findall(self.HOSTER_PATTERN, html)
diff --git a/module/plugins/hooks/MyfastfileComHook.py b/module/plugins/hooks/MyfastfileComHook.py
deleted file mode 100644
index 20a1cfac2..000000000
--- a/module/plugins/hooks/MyfastfileComHook.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class MyfastfileComHook(MultiHook):
- __name__ = "MyfastfileComHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Myfastfile.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def getHosters(self):
- json_data = self.getURL("http://myfastfile.com/api.php", get={'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/NoPremiumPlHook.py b/module/plugins/hooks/NoPremiumPlHook.py
deleted file mode 100644
index b5a007ff9..000000000
--- a/module/plugins/hooks/NoPremiumPlHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class NoPremiumPlHook(MultiHook):
- __name__ = "NoPremiumPlHook"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """NoPremium.pl hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@nopremium.pl")]
-
-
- def getHosters(self):
- hostings = json_loads(self.getURL("https://www.nopremium.pl/clipboard.php?json=3").strip())
- hostings_domains = [domain for row in hostings for domain in row["domains"] if row["sdownload"] == "0"]
-
- self.logDebug(hostings_domains)
-
- return hostings_domains
diff --git a/module/plugins/hooks/OverLoadMeHook.py b/module/plugins/hooks/OverLoadMeHook.py
deleted file mode 100644
index d608a2ecd..000000000
--- a/module/plugins/hooks/OverLoadMeHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class OverLoadMeHook(MultiHook):
- __name__ = "OverLoadMeHook"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
- ("ssl" , "bool" , "Use HTTPS" , True )]
-
- __description__ = """Over-Load.me hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("marley", "marley@over-load.me")]
-
-
- def getHosters(self):
- https = "https" if self.getConfig('ssl') else "http"
- html = self.getURL(https + "://api.over-load.me/hoster.php",
- get={'auth': "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}).replace("\"", "").strip()
- self.logDebug("Hosterlist", html)
-
- return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/module/plugins/hooks/PremiumToHook.py b/module/plugins/hooks/PremiumToHook.py
deleted file mode 100644
index ef2a84223..000000000
--- a/module/plugins/hooks/PremiumToHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class PremiumToHook(MultiHook):
- __name__ = "PremiumToHook"
- __type__ = "hook"
- __version__ = "0.08"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Premium.to hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- def getHosters(self):
- html = self.getURL("http://premium.to/api/hosters.php",
- get={'username': self.account.username, 'password': self.account.password})
- return [x.strip() for x in html.replace("\"", "").split(";")]
diff --git a/module/plugins/hooks/PremiumizeMeHook.py b/module/plugins/hooks/PremiumizeMeHook.py
deleted file mode 100644
index e081fb389..000000000
--- a/module/plugins/hooks/PremiumizeMeHook.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class PremiumizeMeHook(MultiHook):
- __name__ = "PremiumizeMeHook"
- __type__ = "hook"
- __version__ = "0.17"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Premiumize.me hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Florian Franzen", "FlorianFranzen@gmail.com")]
-
-
- def getHosters(self):
- # 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 = self.getURL("https://api.premiumize.me/pm-api/v1.php",
- get={'method': "hosterlist", 'params[login]': user, 'params[pass]': 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']
diff --git a/module/plugins/hooks/PutdriveComHook.py b/module/plugins/hooks/PutdriveComHook.py
deleted file mode 100644
index c3ebf4ff3..000000000
--- a/module/plugins/hooks/PutdriveComHook.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hooks.ZeveraComHook import ZeveraComHook
-
-
-class PutdriveComHook(ZeveraComHook):
- __name__ = "PutdriveComHook"
- __type__ = "hook"
- __version__ = "0.01"
-
- __config__ = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed", "bool" , "Revert to standard download if download fails", False),
- ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
-
- __description__ = """Putdrive.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/module/plugins/hooks/RPNetBizHook.py b/module/plugins/hooks/RPNetBizHook.py
deleted file mode 100644
index 10332948d..000000000
--- a/module/plugins/hooks/RPNetBizHook.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RPNetBizHook(MultiHook):
- __name__ = "RPNetBizHook"
- __type__ = "hook"
- __version__ = "0.14"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """RPNet.biz hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Dman", "dmanugm@gmail.com")]
-
-
- def getHosters(self):
- # Get account data
- user, data = self.account.selectAccount()
-
- res = self.getURL("https://premium.rpnet.biz/client_api.php",
- get={'username': user, 'password': data['password'], 'action': "showHosterList"})
- hoster_list = json_loads(res)
-
- # 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']
diff --git a/module/plugins/hooks/RapideoPlHook.py b/module/plugins/hooks/RapideoPlHook.py
deleted file mode 100644
index 0400f07ba..000000000
--- a/module/plugins/hooks/RapideoPlHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RapideoPlHook(MultiHook):
- __name__ = "RapideoPlHook"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Rapideo.pl hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@rapideo.pl")]
-
-
- def getHosters(self):
- hostings = json_loads(self.getURL("https://www.rapideo.pl/clipboard.php?json=3").strip())
- hostings_domains = [domain for row in hostings for domain in row["domains"] if row["sdownload"] == "0"]
-
- self.logDebug(hostings_domains)
-
- return hostings_domains
diff --git a/module/plugins/hooks/RealdebridComHook.py b/module/plugins/hooks/RealdebridComHook.py
deleted file mode 100644
index aa0c9f640..000000000
--- a/module/plugins/hooks/RealdebridComHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RealdebridComHook(MultiHook):
- __name__ = "RealdebridComHook"
- __type__ = "hook"
- __version__ = "0.46"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
- ("ssl" , "bool" , "Use HTTPS" , True )]
-
- __description__ = """Real-Debrid.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
-
-
- def getHosters(self):
- https = "https" if self.getConfig('ssl') else "http"
- html = self.getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip()
-
- return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/module/plugins/hooks/RehostToHook.py b/module/plugins/hooks/RehostToHook.py
deleted file mode 100644
index a2415129a..000000000
--- a/module/plugins/hooks/RehostToHook.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class RehostToHook(MultiHook):
- __name__ = "RehostToHook"
- __type__ = "hook"
- __version__ = "0.50"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Rehost.to hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def getHosters(self):
- user, data = self.account.selectAccount()
- html = self.getURL("http://rehost.to/api.php",
- get={'cmd' : "get_supported_och_dl",
- 'long_ses': self.account.getAccountInfo(user)['session']})
- return [x.strip() for x in html.replace("\"", "").split(",")]
diff --git a/module/plugins/hooks/RestartFailed.py b/module/plugins/hooks/RestartFailed.py
deleted file mode 100644
index cab986d9e..000000000
--- a/module/plugins/hooks/RestartFailed.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Hook import Hook
-
-
-class RestartFailed(Hook):
- __name__ = "RestartFailed"
- __type__ = "hook"
- __version__ = "1.58"
-
- __config__ = [("interval", "int", "Check interval in minutes", 90)]
-
- __description__ = """Restart all the failed downloads in queue"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- # event_list = ["pluginConfigChanged"]
-
- MIN_CHECK_INTERVAL = 15 * 60 #: 15 minutes
-
-
- # def pluginConfigChanged(self, plugin, name, value):
- # if name == "interval":
- # interval = value * 60
- # if self.MIN_CHECK_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.logDebug(_("Restart failed downloads"))
- self.core.api.restartFailed()
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.interval = self.MIN_CHECK_INTERVAL
-
-
- def coreReady(self):
- # self.pluginConfigChanged(self.__name__, "interval", self.getConfig('interval'))
- self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60)
diff --git a/module/plugins/hooks/SimplyPremiumComHook.py b/module/plugins/hooks/SimplyPremiumComHook.py
deleted file mode 100644
index 116e3a76e..000000000
--- a/module/plugins/hooks/SimplyPremiumComHook.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class SimplyPremiumComHook(MultiHook):
- __name__ = "SimplyPremiumComHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Simply-Premium.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
-
-
- def getHosters(self):
- json_data = self.getURL("http://www.simply-premium.com/api/hosts.php", get={'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/SimplydebridComHook.py b/module/plugins/hooks/SimplydebridComHook.py
deleted file mode 100644
index 01629df99..000000000
--- a/module/plugins/hooks/SimplydebridComHook.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class SimplydebridComHook(MultiHook):
- __name__ = "SimplydebridComHook"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Simply-Debrid.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def getHosters(self):
- html = self.getURL("http://simply-debrid.com/api.php", get={'list': 1})
- return [x.strip() for x in html.rstrip(';').replace("\"", "").split(";")]
diff --git a/module/plugins/hooks/SkipRev.py b/module/plugins/hooks/SkipRev.py
deleted file mode 100644
index 7901ca540..000000000
--- a/module/plugins/hooks/SkipRev.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-import urlparse
-
-from types import MethodType
-
-from module.PyFile import PyFile
-from module.plugins.Hook import Hook
-from module.plugins.Plugin import SkipDownload
-
-
-class SkipRev(Hook):
- __name__ = "SkipRev"
- __type__ = "hook"
- __version__ = "0.29"
-
- __config__ = [("mode" , "Auto;Manual", "Choose recovery archives to skip" , "Auto"),
- ("revtokeep", "int" , "Number of recovery archives to keep for package", 0 )]
-
- __description__ = """Skip recovery archives (.rev)"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- @staticmethod
- def _setup(self):
- self.pyfile.plugin._setup()
- if self.pyfile.hasStatus("skipped"):
- raise SkipDownload(self.pyfile.statusname or self.pyfile.pluginname)
-
-
- def _name(self, pyfile):
- if hasattr(pyfile.pluginmodule, "getInfo"): #@NOTE: getInfo is deprecated in 0.4.10
- return pyfile.pluginmodule.getInfo([pyfile.url]).next()[0]
- else:
- self.logWarning("Unable to grab file name")
- return urlparse.urlparse(urllib.unquote(pyfile.url)).path.split('/')[-1]
-
-
- def _pyfile(self, link):
- return PyFile(self.core.files,
- link.fid,
- link.url,
- link.name,
- link.size,
- link.status,
- link.error,
- link.plugin,
- link.packageID,
- link.order)
-
-
- def downloadPreparing(self, pyfile):
- name = self._name(pyfile)
-
- if pyfile.statusname is _("unskipped") or not name.endswith(".rev") or not ".part" in name:
- return
-
- revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
-
- if revtokeep:
- status_list = (1, 4, 8, 9, 14) if revtokeep < 0 else (1, 3, 4, 8, 9, 14)
- pyname = re.compile(r'%s\.part\d+\.rev$' % name.rsplit('.', 2)[0].replace('.', '\.'))
-
- queued = [True for link in self.core.api.getPackageData(pyfile.package().id).links \
- if link.status not in status_list and pyname.match(link.name)].count(True)
-
- if not queued or queued < revtokeep: #: keep one rev at least in auto mode
- return
-
- pyfile.setCustomStatus("SkipRev", "skipped")
-
- if not hasattr(pyfile.plugin, "_setup"):
- # Work-around: inject status checker inside the preprocessing routine of the plugin
- pyfile.plugin._setup = pyfile.plugin.setup
- pyfile.plugin.setup = MethodType(self._setup, pyfile.plugin)
-
-
- def downloadFailed(self, pyfile):
- #: Check if pyfile is still "failed",
- # maybe might has been restarted in meantime
- if pyfile.status != 8 or pyfile.name.rsplit('.', 1)[-1].strip() not in ("rar", "rev"):
- return
-
- revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
-
- if not revtokeep:
- return
-
- pyname = re.compile(r'%s\.part\d+\.rev$' % pyfile.name.rsplit('.', 2)[0].replace('.', '\.'))
-
- for link in self.core.api.getPackageData(pyfile.package().id).links:
- if link.status is 4 and pyname.match(link.name):
- pylink = self._pyfile(link)
-
- if revtokeep > -1 or pyfile.name.endswith(".rev"):
- pylink.setStatus("queued")
- else:
- pylink.setCustomStatus(_("unskipped"), "queued")
-
- self.core.files.save()
- pylink.release()
- return
diff --git a/module/plugins/hooks/SmoozedComHook.py b/module/plugins/hooks/SmoozedComHook.py
deleted file mode 100644
index 24b7c8df0..000000000
--- a/module/plugins/hooks/SmoozedComHook.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class SmoozedComHook(MultiHook):
- __name__ = "SmoozedComHook"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Smoozed.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("", "")]
-
-
- def getHosters(self):
- user, data = self.account.selectAccount()
- return self.account.getAccountInfo(user)["hosters"]
diff --git a/module/plugins/hooks/UnSkipOnFail.py b/module/plugins/hooks/UnSkipOnFail.py
deleted file mode 100644
index 9059d0350..000000000
--- a/module/plugins/hooks/UnSkipOnFail.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.PyFile import PyFile
-from module.plugins.Hook import Hook
-
-
-class UnSkipOnFail(Hook):
- __name__ = "UnSkipOnFail"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("activated", "bool", "Activated", True)]
-
- __description__ = """Restart skipped duplicates when download fails"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def downloadFailed(self, pyfile):
- #: Check if pyfile is still "failed",
- # maybe might has been restarted in meantime
- if pyfile.status != 8:
- return
-
- msg = _("Looking for skipped duplicates of: %s (pid:%s)")
- self.logInfo(msg % (pyfile.name, pyfile.package().id))
-
- link = self.findDuplicate(pyfile)
- if link:
- self.logInfo(_("Queue found duplicate: %s (pid:%s)") % (link.name, link.packageID))
-
- #: 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.
- pylink = self._pyfile(link)
-
- pylink.setCustomStatus(_("unskipped"), "queued")
-
- self.core.files.save()
- pylink.release()
-
- else:
- self.logInfo(_("No duplicates found"))
-
-
- def findDuplicate(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 (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.
- """
- queue = self.core.api.getQueue() #: get packages (w/o files, as most file data is useless here)
-
- for package in queue:
- #: check if package-folder equals pyfile's package folder
- if package.folder != pyfile.package().folder:
- continue
-
- #: now get packaged data w/ files/links
- pdata = self.core.api.getPackageData(package.pid)
- for link in pdata.links:
- #: check if link is "skipped"
- if link.status != 4:
- continue
-
- #: check if link name collides with pdata's name
- #: AND at last check if it is not pyfile itself
- if link.name == pyfile.name and link.fid != pyfile.id:
- return link
-
-
- def _pyfile(self, link):
- return PyFile(self.core.files,
- link.fid,
- link.url,
- link.name,
- link.size,
- link.status,
- link.error,
- link.plugin,
- link.packageID,
- link.order)
diff --git a/module/plugins/hooks/UpdateManager.py b/module/plugins/hooks/UpdateManager.py
deleted file mode 100644
index 5779e7c80..000000000
--- a/module/plugins/hooks/UpdateManager.py
+++ /dev/null
@@ -1,329 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import re
-import sys
-import time
-
-from operator import itemgetter
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Expose, Hook, threaded
-from module.utils import save_join
-
-
-# Case-sensitive os.path.exists
-def exists(path):
- if os.path.exists(path):
- if os.name == 'nt':
- dir, name = os.path.split(path)
- return name in os.listdir(dir)
- else:
- return True
- else:
- return False
-
-
-class UpdateManager(Hook):
- __name__ = "UpdateManager"
- __type__ = "hook"
- __version__ = "0.51"
-
- __config__ = [("activated" , "bool", "Activated" , True ),
- ("checkinterval", "int" , "Check interval in hours" , 8 ),
- ("autorestart" , "bool", "Auto-restart pyLoad when required" , True ),
- ("checkonstart" , "bool", "Check for updates on startup" , True ),
- ("checkperiod" , "bool", "Check for updates periodically" , True ),
- ("reloadplugins", "bool", "Monitor plugin code changes in debug mode", True ),
- ("nodebugupdate", "bool", "Don't update plugins in debug mode" , False)]
-
- __description__ = """ Check for updates """
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0
-
- SERVER_URL = "http://updatemanager.pyload.org"
- MIN_CHECK_INTERVAL = 3 * 60 * 60 #: 3 hours
-
- event_list = ["allDownloadsProcessed"]
-
-
- def coreReady(self):
- if self.checkonstart:
- self.update()
-
- self.initPeriodical()
-
-
- def setup(self):
- self.interval = 10
- self.info = {'pyload': False, 'version': None, 'plugins': False, 'last_check': time.time()}
- self.mtimes = {} #: store modification time for each plugin
-
- if self.getConfig('checkonstart'):
- self.core.api.pauseServer()
- self.checkonstart = True
- else:
- self.checkonstart = False
-
- self.do_restart = False
-
-
- def allDownloadsProcessed(self):
- if self.do_restart is True:
- self.logWarning(_("Downloads are done, restarting pyLoad to reload the updated plugins"))
- self.core.api.restart()
-
-
- def periodical(self):
- if self.core.debug:
- if self.getConfig('reloadplugins'):
- self.autoreloadPlugins()
-
- if self.getConfig('nodebugupdate'):
- return
-
- if self.getConfig('checkperiod') \
- and time.time() - max(self.MIN_CHECK_INTERVAL, self.getConfig('checkinterval') * 60 * 60) > self.info['last_check']:
- self.update()
-
-
- @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 os.path.isfile(f):
- continue
-
- mtime = os.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 server_response(self):
- try:
- return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
-
- except Exception:
- self.logWarning(_("Unable to retrieve server to get updates"))
-
-
- @Expose
- @threaded
- def update(self):
- """ check for updates """
-
- self.core.api.pauseServer()
-
- if self._update() is 2 and self.getConfig('autorestart'):
- if not self.core.api.statusDownloads():
- self.core.api.restart()
- else:
- self.do_restart = True
- self.logWarning(_("Downloads are active, will restart once the download is done"))
- else:
- self.core.api.unpauseServer()
-
-
- def _update(self):
- data = self.server_response()
-
- self.info['last_check'] = time.time()
-
- if not data:
- exitcode = 0
-
- elif data[0] == "None":
- self.logInfo(_("No new pyLoad version available"))
- exitcode = self._updatePlugins(data[1:])
-
- elif onlyplugin:
- exitcode = 0
-
- else:
- self.logInfo(_("*** New pyLoad Version %s available ***") % data[0])
- self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
- self.info['pyload'] = True
- self.info['version'] = data[0]
- exitcode = 3
-
- # Exit codes:
- # -1 = No plugin updated, new pyLoad version available
- # 0 = No plugin updated
- # 1 = Plugins updated
- # 2 = Plugins updated, but restart required
- return exitcode
-
-
- def _updatePlugins(self, data):
- """ check for plugin updates """
-
- exitcode = 0
- updated = []
-
- url = data[0]
- schema = data[1].split('|')
-
- VERSION = re.compile(r'__version__.*=.*("|\')([\d.]+)')
-
- if "BLACKLIST" in data:
- blacklist = data[data.index('BLACKLIST') + 1:]
- updatelist = data[2:data.index('BLACKLIST')]
- else:
- blacklist = []
- updatelist = data[2:]
-
- updatelist = [dict(zip(schema, x.split('|'))) for x in updatelist]
- blacklist = [dict(zip(schema, x.split('|'))) for x in blacklist]
-
- if blacklist:
- type_plugins = [(plugin['type'], plugin['name'].rsplit('.', 1)[0]) for plugin in blacklist]
-
- # Protect UpdateManager from self-removing
- try:
- type_plugins.remove(("hook", "UpdateManager"))
- except ValueError:
- pass
-
- for t, n in type_plugins:
- for idx, plugin in enumerate(updatelist):
- if n == plugin['name'] and t == plugin['type']:
- updatelist.pop(idx)
- break
-
- for t, n in self.removePlugins(sorted(type_plugins)):
- self.logInfo(_("Removed blacklisted plugin: [%(type)s] %(name)s") % {
- 'type': t,
- 'name': n,
- })
-
- for plugin in sorted(updatelist, key=itemgetter("type", "name")):
- filename = plugin['name']
- prefix = plugin['type']
- version = plugin['version']
-
- if filename.endswith(".pyc"):
- name = filename[:filename.find("_")]
- else:
- name = filename.replace(".py", "")
-
- #@TODO: Remove in 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 plugin: [%(type)s] %(name)s (v%(newver).2f)"
- elif newver > oldver:
- msg = "New version of plugin: [%(type)s] %(name)s (v%(oldver).2f -> v%(newver).2f)"
- else:
- continue
-
- self.logInfo(_(msg) % {'type' : type,
- 'name' : name,
- 'oldver': oldver,
- 'newver': newver})
- try:
- content = getURL(url % plugin)
- m = VERSION.search(content)
-
- if m and m.group(2) == version:
- with open(save_join("userplugins", prefix, filename), "wb") as f:
- f.write(content)
-
- updated.append((prefix, name))
- else:
- raise Exception, _("Version mismatch")
-
- except Exception, e:
- self.logError(_("Error updating plugin: %s") % filename, e)
-
- if updated:
- self.logInfo(_("*** Plugins updated ***"))
-
- if self.core.pluginManager.reloadPlugins(updated):
- exitcode = 1
- else:
- self.logWarning(_("pyLoad restart required to reload the updated plugins"))
- self.info['plugins'] = True
- exitcode = 2
-
- self.manager.dispatchEvent("plugin_updated", updated)
- else:
- self.logInfo(_("No plugin updates available"))
-
- # Exit codes:
- # 0 = No plugin updated
- # 1 = Plugins updated
- # 2 = Plugins updated, but restart required
- return exitcode
-
-
- @Expose
- def removePlugins(self, type_plugins):
- """ delete plugins from disk """
-
- if not type_plugins:
- return
-
- removed = set()
-
- self.logDebug("Requested deletion of plugins: %s" % type_plugins)
-
- for type, name in type_plugins:
- rootplugins = os.path.join(pypath, "module", "plugins")
-
- for dir in ("userplugins", rootplugins):
- py_filename = save_join(dir, type, name + ".py")
- pyc_filename = py_filename + "c"
-
- if type == "hook":
- try:
- self.manager.deactivateHook(name)
-
- except Exception, e:
- self.logDebug(e)
-
- for filename in (py_filename, pyc_filename):
- if not exists(filename):
- continue
-
- try:
- os.remove(filename)
-
- except OSError, e:
- self.logError(_("Error removing: %s") % filename, e)
-
- else:
- id = (type, name)
- removed.add(id)
-
- return list(removed) #: return a list of the plugins successfully removed
diff --git a/module/plugins/hooks/UserAgentSwitcher.py b/module/plugins/hooks/UserAgentSwitcher.py
deleted file mode 100644
index 912c2ef09..000000000
--- a/module/plugins/hooks/UserAgentSwitcher.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import pycurl
-import random
-
-from module.plugins.Hook import Hook
-from module.utils import fs_encode
-
-
-class UserAgentSwitcher(Hook):
- __name__ = "UserAgentSwitcher"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("activated", "bool", "Activated" , True ),
- ("uaf" , "file", "Random user-agents file" , "" ),
- ("uar" , "bool", "Random user-agent" , False ),
- ("uas" , "str" , "Custom user-agent string", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0")]
-
- __description__ = """Custom user-agent"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def downloadPreparing(self, pyfile):
- uar = self.getConfig('uar')
- uaf = fs_encode(self.getConfig('uaf'))
-
- if uar and os.path.isfile(uaf):
- with open(uaf) as f:
- uas = random.choice([ua for ua in f.read().splitlines()])
- else:
- uas = self.getConfig('uas')
-
- if uas:
- self.logDebug("Use custom user-agent string: " + uas)
- pyfile.plugin.req.http.c.setopt(pycurl.USERAGENT, uas.encode('utf-8'))
diff --git a/module/plugins/hooks/WindowsPhoneNotify.py b/module/plugins/hooks/WindowsPhoneNotify.py
deleted file mode 100644
index e61057f9f..000000000
--- a/module/plugins/hooks/WindowsPhoneNotify.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import httplib
-import time
-
-from module.plugins.Hook import Hook, Expose
-
-
-class WindowsPhoneNotify(Hook):
- __name__ = "WindowsPhoneNotify"
- __type__ = "hook"
- __version__ = "0.09"
-
- __config__ = [("id" , "str" , "Push ID" , "" ),
- ("url" , "str" , "Push url" , "" ),
- ("notifycaptcha" , "bool", "Notify captcha request" , True ),
- ("notifypackage" , "bool", "Notify package finished" , True ),
- ("notifyprocessed", "bool", "Notify packages processed" , True ),
- ("notifyupdate" , "bool", "Notify plugin updates" , True ),
- ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
- ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
- ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
- ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
-
- __description__ = """Send push notifications to Windows Phone"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt" , "phone-support@hotmail.de"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- event_list = ["allDownloadsProcessed", "plugin_updated"]
- interval = 0 #@TODO: Remove in 0.4.10
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
- self.last_notify = 0
- self.notifications = 0
-
-
- def plugin_updated(self, type_plugins):
- if not self.getConfig('notifyupdate'):
- return
-
- self.notify(_("Plugins updated"), str(type_plugins))
-
-
- def coreExiting(self):
- if not self.getConfig('notifyexit'):
- return
-
- if self.core.do_restart:
- self.notify(_("Restarting pyLoad"))
- else:
- self.notify(_("Exiting pyLoad"))
-
-
- def newCaptchaTask(self, task):
- if not self.getConfig('notifycaptcha'):
- return
-
- self.notify(_("Captcha"), _("New request waiting user input"))
-
-
- def packageFinished(self, pypack):
- if self.getConfig('notifypackage'):
- self.notify(_("Package finished"), pypack.name)
-
-
- def allDownloadsProcessed(self):
- if not self.getConfig('notifyprocessed'):
- return
-
- if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
- self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
- else:
- self.notify(_("All packages finished"))
-
-
- def getXmlData(self, msg):
- return ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> "
- "<wp:Toast> <wp:Text1>pyLoad</wp:Text1> <wp:Text2>%s</wp:Text2> "
- "</wp:Toast> </wp:Notification>" % msg)
-
-
- @Expose
- def notify(self,
- event,
- msg="",
- key=(self.getConfig('id'), self.getConfig('url'))):
-
- id, url = key
-
- if not id or not url:
- return
-
- if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
- return
-
- elapsed_time = time.time() - self.last_notify
-
- if elapsed_time < self.getConf("sendtimewait"):
- return
-
- if elapsed_time > 60:
- self.notifications = 0
-
- elif self.notifications >= self.getConf("sendpermin"):
- return
-
-
- request = self.getXmlData("%s: %s" % (event, msg) if msg else event)
- webservice = httplib.HTTP(url)
-
- webservice.putrequest("POST", id)
- 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.last_notify = time.time()
- self.notifications += 1
diff --git a/module/plugins/hooks/XFileSharingPro.py b/module/plugins/hooks/XFileSharingPro.py
deleted file mode 100644
index d80515a22..000000000
--- a/module/plugins/hooks/XFileSharingPro.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hook import Hook
-
-
-class XFileSharingPro(Hook):
- __name__ = "XFileSharingPro"
- __type__ = "hook"
- __version__ = "0.37"
-
- __config__ = [("activated" , "bool", "Activated" , True ),
- ("use_hoster_list" , "bool", "Load listed hosters only" , False),
- ("use_crypter_list", "bool", "Load listed crypters only" , False),
- ("use_builtin_list", "bool", "Load built-in plugin list" , True ),
- ("hoster_list" , "str" , "Hoster list (comma separated)" , "" ),
- ("crypter_list" , "str" , "Crypter list (comma separated)", "" )]
-
- __description__ = """Load XFileSharingPro based hosters and crypter which don't need a own plugin to run"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- # event_list = ["pluginConfigChanged"]
- interval = 0 #@TODO: Remove in 0.4.10
- regexp = {'hoster' : (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:embed-)?\w{12}(?:\W|$)',
- r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:embed-)?\w+'),
- 'crypter': (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:user|folder)s?/\w+',
- r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:user|folder)s?/\w+')}
-
- HOSTER_BUILTIN = [#WORKING HOSTERS:
- "backin.net", "eyesfile.ca", "file4safe.com", "fileband.com", "filedwon.com", "fileparadox.in",
- "filevice.com", "hostingbulk.com", "junkyvideo.com", "linestorage.com", "ravishare.com", "ryushare.com",
- "salefiles.com", "sendmyway.com", "sharesix.com", "thefile.me", "verzend.be", "xvidstage.com",
- #NOT TESTED:
- "101shared.com", "4upfiles.com", "filemaze.ws", "filenuke.com", "linkzhost.com", "mightyupload.com",
- "rockdizfile.com", "sharebeast.com", "sharerepo.com", "shareswift.com", "uploadbaz.com", "uploadc.com",
- "vidbull.com", "worldbytez.com", "zalaa.com", "zomgupload.com",
- #NOT WORKING:
- "amonshare.com", "banicrazy.info", "boosterking.com", "host4desi.com", "laoupload.com", "rd-fs.com"]
- CRYPTER_BUILTIN = ["junocloud.me", "rapidfileshare.net"]
-
-
- # def pluginConfigChanged(self, plugin, name, value):
- # self.loadPattern()
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
-
- def coreReady(self):
- self.loadPattern()
-
-
- def loadPattern(self):
- use_builtin_list = self.getConfig('use_builtin_list')
-
- for type, plugin in (("hoster", "XFileSharingPro"),
- ("crypter", "XFileSharingProFolder")):
- every_plugin = not self.getConfig("use_%s_list" % type)
-
- if every_plugin:
- self.logInfo(_("Handling any %s I can!") % type)
- pattern = self.regexp[type][0]
- else:
- plugins = self.getConfig('%s_list' % type)
- plugin_set = set(plugins.replace(' ', '').replace('\\', '').replace('|', ',').replace(';', ',').lower().split(','))
-
- if use_builtin_list:
- plugin_set |= set(x.lower() for x in getattr(self, "%s_BUILTIN" % type.upper()))
-
- plugin_set -= set(('', u''))
-
- if not plugin_set:
- self.logInfo(_("No %s to handle") % type)
- self._unload(type, plugin)
- return
-
- match_list = '|'.join(sorted(plugin_set))
-
- len_match_list = len(plugin_set)
- self.logInfo(_("Handling %d %s%s: %s") % (len_match_list,
- type,
- "" if len_match_list == 1 else "s",
- match_list.replace('|', ', ')))
-
- pattern = self.regexp[type][1] % match_list.replace('.', '\.')
-
- dict = self.core.pluginManager.plugins[type][plugin]
- dict['pattern'] = pattern
- dict['re'] = re.compile(pattern)
-
- self.logDebug("Loaded %s pattern: %s" % (type, pattern))
-
-
- def _unload(self, type, plugin):
- dict = self.core.pluginManager.plugins[type][plugin]
- dict['pattern'] = r'^unmatchable$'
- dict['re'] = re.compile(dict['pattern'])
-
-
- def unload(self):
- # self.unloadHoster("BasePlugin")
- for type, plugin in (("hoster", "XFileSharingPro"),
- ("crypter", "XFileSharingProFolder")):
- self._unload(type, plugin)
-
-
- def unloadHoster(self, hoster):
- hdict = self.core.pluginManager.hosterPlugins[hoster]
- if "new_name" in hdict and hdict['new_name'] == "XFileSharingPro":
- if "module" in hdict:
- hdict.pop('module', None)
-
- if "new_module" in hdict:
- hdict.pop('new_module', None)
- hdict.pop('new_name', None)
-
- return True
- else:
- return False
-
-
- # def downloadFailed(self, pyfile):
- # if pyfile.pluginname == "BasePlugin" \
- # and pyfile.hasStatus("failed") \
- # and not self.getConfig('use_hoster_list') \
- # and self.unloadHoster("BasePlugin"):
- # self.logDebug("Unloaded XFileSharingPro from BasePlugin")
- # pyfile.setStatus("queued")
diff --git a/module/plugins/hooks/XMPPInterface.py b/module/plugins/hooks/XMPPInterface.py
deleted file mode 100644
index b61428392..000000000
--- a/module/plugins/hooks/XMPPInterface.py
+++ /dev/null
@@ -1,252 +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__ = [("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"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "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 Exception:
- 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 Exception:
- pass
-
-
- def run(self):
- # connect to IRC etc.
- self.connect()
- try:
- self.loop()
- except Exception, ex:
- self.logError(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("*** State changed: %s %r ***" % (state, arg))
-
-
- def disconnected(self):
- self.logDebug("Client was disconnected")
-
-
- def stream_closed(self, stream):
- self.logDebug("Stream was closed", stream)
-
-
- def stream_error(self, err):
- self.logDebug("Stream Error", 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("Message from %s received." % unicode(stanza.get_from()))
- self.logDebug("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 Exception:
- 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(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("Send message to", 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/ZeveraComHook.py b/module/plugins/hooks/ZeveraComHook.py
deleted file mode 100644
index 21c1741d2..000000000
--- a/module/plugins/hooks/ZeveraComHook.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHook import MultiHook
-
-
-class ZeveraComHook(MultiHook):
- __name__ = "ZeveraComHook"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
- ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Zevera.com hook plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- def getHosters(self):
- html = self.account.api_response(pyreq.getHTTPRequest(timeout=120), cmd="gethosters")
- return [x.strip() for x in html.split(",")]
diff --git a/module/plugins/hooks/__init__.py b/module/plugins/hooks/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/hooks/__init__.py
+++ /dev/null
diff --git a/module/plugins/hoster/AlldebridCom.py b/module/plugins/hoster/AlldebridCom.py
deleted file mode 100644
index 2ed09f58c..000000000
--- a/module/plugins/hoster/AlldebridCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class AlldebridCom(MultiHoster):
- __name__ = "AlldebridCom"
- __type__ = "hoster"
- __version__ = "0.46"
-
- __pattern__ = r'https?://(?:www\.|s\d+\.)?alldebrid\.com/dl/[\w^_]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Alldebrid.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Andy Voigt", "spamsales@online.de")]
-
-
- def setup(self):
- self.chunkLimit = 16
-
-
- def handlePremium(self, pyfile):
- password = self.getPassword()
-
- data = json_loads(self.load("http://www.alldebrid.com/service.php",
- get={'link': pyfile.url, 'json': "true", 'pw': password}))
-
- self.logDebug("Json data", 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'])
- self.link = data['link']
-
- if self.getConfig('ssl'):
- self.link = self.link.replace("http://", "https://")
- else:
- self.link = self.link.replace("https://", "http://")
-
-
-getInfo = create_getInfo(AlldebridCom)
diff --git a/module/plugins/hoster/AndroidfilehostCom.py b/module/plugins/hoster/AndroidfilehostCom.py
deleted file mode 100644
index 08005de0f..000000000
--- a/module/plugins/hoster/AndroidfilehostCom.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# https://www.androidfilehost.com/?fid=95916177934518197
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class AndroidfilehostCom(SimpleHoster):
- __name__ = "AndroidfilehostCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?androidfilehost\.com/\?fid=\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Androidfilehost.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<br />(?P<N>.*?)</h1>'
- SIZE_PATTERN = r'<h4>size</h4>\s*<p>(?P<S>[\d.,]+)(?P<U>[\w^_]+)</p>'
- HASHSUM_PATTERN = r'<h4>(?P<T>.*?)</h4>\s*<p><code>(?P<H>.*?)</code></p>'
-
- OFFLINE_PATTERN = r'404 not found'
-
- WAIT_PATTERN = r'users must wait <strong>(\d+) secs'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- wait = re.search(self.WAIT_PATTERN, self.html)
- self.logDebug("Waiting time: %s seconds" % wait.group(1))
-
- fid = re.search(r'id="fid" value="(\d+)" />', self.html).group(1)
- self.logDebug("fid: %s" % fid)
-
- html = self.load("https://www.androidfilehost.com/libs/otf/mirrors.otf.php",
- post={'submit': 'submit',
- 'action': 'getdownloadmirrors',
- 'fid' : fid},
- decode=True)
-
- self.link = re.findall('"url":"(.*?)"', html)[0].replace("\\", "")
- mirror_host = self.link.split("/")[2]
-
- self.logDebug("Mirror Host: %s" % mirror_host)
-
- html = self.load("https://www.androidfilehost.com/libs/otf/stats.otf.php",
- get={'fid' : fid,
- 'w' : 'download',
- 'mirror': mirror_host},
- decode=True)
-
-
-getInfo = create_getInfo(AndroidfilehostCom)
diff --git a/module/plugins/hoster/BasePlugin.py b/module/plugins/hoster/BasePlugin.py
deleted file mode 100644
index 2228516aa..000000000
--- a/module/plugins/hoster/BasePlugin.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-import urlparse
-
-from module.network.HTTPRequest import BadHeader
-from module.plugins.internal.SimpleHoster import create_getInfo, getFileURL
-from module.plugins.Hoster import Hoster
-
-
-class BasePlugin(Hoster):
- __name__ = "BasePlugin"
- __type__ = "hoster"
- __version__ = "0.43"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """Base Plugin when any other didnt fit"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- @classmethod
- def getInfo(cls, url="", html=""): #@TODO: Move to hoster class in 0.4.10
- url = urllib.unquote(url)
- url_p = urlparse.urlparse(url)
- return {'name' : (url_p.path.split('/')[-1]
- or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
- or url_p.netloc.split('.', 1)[0]),
- 'size' : 0,
- 'status': 3 if url else 8,
- 'url' : url}
-
-
- def setup(self):
- self.chunkLimit = -1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def process(self, pyfile):
- """main function"""
-
- pyfile.name = self.getInfo(pyfile.url)['name']
-
- if not pyfile.url.startswith("http"):
- self.fail(_("No plugin matched"))
-
- for _i in xrange(5):
- try:
- link = getFileURL(self, urllib.unquote(pyfile.url))
-
- if link:
- self.download(link, ref=False, disposition=True)
- else:
- self.fail(_("File not found"))
-
- except BadHeader, e:
- if e.code is 404:
- self.offline()
-
- elif e.code in (401, 403):
- self.logDebug("Auth required", "Received HTTP status code: %d" % e.code)
-
- account = self.core.accountManager.getAccountPlugin('Http')
- servers = [x['login'] for x in account.getAllAccounts()]
- server = urlparse.urlparse(pyfile.url).netloc
-
- if server in servers:
- self.logDebug("Logging on to %s" % server)
- self.req.addAuth(account.getAccountData(server)['password'])
- else:
- pwd = self.getPassword()
- if ':' in pwd:
- self.req.addAuth(pwd)
- else:
- self.fail(_("Authorization required"))
- else:
- self.fail(e)
- else:
- break
- else:
- self.fail(_("No file downloaded")) #@TODO: Move to hoster class in 0.4.10
-
- errmsg = self.checkDownload({'Empty file' : re.compile(r'\A\s*\Z'),
- 'Html error' : re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)'),
- 'Html file' : re.compile(r'\A\s*<!DOCTYPE html'),
- 'Request error': re.compile(r'([Aa]n error occured while processing your request)')})
- if not errmsg:
- return
-
- try:
- errmsg += " | " + self.lastCheck.group(1).strip()
- except Exception:
- pass
-
- self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
- self.retry(3, 60, errmsg)
-
-
-getInfo = create_getInfo(BasePlugin)
diff --git a/module/plugins/hoster/BasketbuildCom.py b/module/plugins/hoster/BasketbuildCom.py
deleted file mode 100644
index 89e4d39f9..000000000
--- a/module/plugins/hoster/BasketbuildCom.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# https://s.basketbuild.com/filedl/devs?dev=pacman&dl=pacman/falcon/RC-3/pac_falcon-RC-3-20141103.zip
-# https://s.basketbuild.com/filedl/gapps?dl=gapps-gb-20110828-signed.zip
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class BasketbuildCom(SimpleHoster):
- __name__ = "BasketbuildCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?(?:\w\.)?basketbuild\.com/filedl/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """basketbuild.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'File Name:</strong> (?P<N>.+?)<br/>'
- SIZE_PATTERN = r'File Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'404 - Page Not Found'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- try:
- link1 = re.search(r'href="(.+dlgate/.+)"', self.html).group(1)
- self.html = self.load(link1)
-
- except AttributeError:
- self.error(_("Hop #1 not found"))
-
- else:
- self.logDebug("Next hop: %s" % link1)
-
- try:
- wait = re.search(r'var sec = (\d+)', self.html).group(1)
- self.logDebug("Wait %s seconds" % wait)
- self.wait(wait)
-
- except AttributeError:
- self.logDebug("No wait time found")
-
- try:
- self.link = re.search(r'id="dlLink">\s*<a href="(.+?)"', self.html).group(1)
-
- except AttributeError:
- self.error(_("DL-Link not found"))
-
-
-getInfo = create_getInfo(BasketbuildCom)
diff --git a/module/plugins/hoster/BayfilesCom.py b/module/plugins/hoster/BayfilesCom.py
deleted file mode 100644
index 92058d634..000000000
--- a/module/plugins/hoster/BayfilesCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class BayfilesCom(DeadHoster):
- __name__ = "BayfilesCom"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>\w+/\w+/[^/]+)'
- __config__ = []
-
- __description__ = """Bayfiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(BayfilesCom)
diff --git a/module/plugins/hoster/BezvadataCz.py b/module/plugins/hoster/BezvadataCz.py
deleted file mode 100644
index fbb17635c..000000000
--- a/module/plugins/hoster/BezvadataCz.py
+++ /dev/null
@@ -1,95 +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.26"
-
- __pattern__ = r'http://(?:www\.)?bezvadata\.cz/stahnout/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """BezvaData.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>'
- SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>'
- OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- #download button
- m = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html)
- if m is None:
- self.error(_("Page 1 URL not found"))
- url = "http://bezvadata.cz%s" % m.group(1)
-
- #captcha form
- self.html = self.load(url)
- self.checkErrors()
- for _i in xrange(5):
- action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm')
- if not inputs:
- self.error(_("FreeForm"))
-
- m = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html)
- if m is None:
- self.error(_("Wrong captcha image"))
-
- #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.error(_("Page 2 URL not found"))
- 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))) if m else 120
- self.wait(wait_time, False)
-
- self.link = 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()
-
- self.info.pop('error', None)
-
-
- 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 efe2c22a3..000000000
--- a/module/plugins/hoster/BillionuploadsCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class BillionuploadsCom(DeadHoster):
- __name__ = "BillionuploadsCom"
- __type__ = "hoster"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?billionuploads\.com/\w{12}'
-
- __description__ = """Billionuploads.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(BillionuploadsCom)
diff --git a/module/plugins/hoster/BitshareCom.py b/module/plugins/hoster/BitshareCom.py
deleted file mode 100644
index 79aaedcd9..000000000
--- a/module/plugins/hoster/BitshareCom.py
+++ /dev/null
@@ -1,159 +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.53"
-
- __pattern__ = r'http://(?:www\.)?bitshare\.com/(files/)?(?(1)|\?f=)(?P<ID>\w+)(?(1)/(?P<NAME>.+?)\.html)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Bitshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Paul King", None),
- ("fragonib", "fragonib[AT]yahoo[DOT]es")]
-
-
- COOKIES = [("bitshare.com", "language_selection", "EN")]
-
- INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</h1>'
- OFFLINE_PATTERN = r'[Ff]ile (not available|was deleted|was not found)'
-
- AJAXID_PATTERN = r'var ajaxdl = "(.*?)";'
- TRAFFIC_USED_UP = r'Your Traffic is used up for today'
-
-
- def setup(self):
- self.multiDL = self.premium
- self.chunkLimit = 1
-
-
- def process(self, pyfile):
- if self.premium:
- self.account.relogin(self.user)
-
- # 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.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.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
- self.link = self.getDownloadUrl()
-
- if self.checkDownload({"error": ">Error occured<"}):
- 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, just_header=True)
- if 'location' in header:
- return header['location']
-
- # Get download info
- self.logDebug("Getting download info")
- res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request": "generateID", "ajaxid": self.ajaxid})
-
- self.handleErrors(res, ':')
-
- parts = res.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")
- recaptcha = ReCaptcha(self)
-
- # Try up to 3 times
- for i in xrange(3):
- response, challenge = recaptcha.challenge()
- res = 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" : response})
- if self.handleCaptchaErrors(res):
- break
-
- # Get download URL
- self.logDebug("Getting download url")
- res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request": "getDownloadURL", "ajaxid": self.ajaxid})
-
- self.handleErrors(res, '#')
-
- url = res.split("#")[-1]
-
- return url
-
-
- def handleErrors(self, res, separator):
- self.logDebug("Checking response [%s]" % res)
- if "ERROR:Session timed out" in res:
- self.retry()
- elif "ERROR" in res:
- msg = res.split(separator)[-1]
- self.fail(msg)
-
-
- def handleCaptchaErrors(self, res):
- self.logDebug("Result of captcha resolving [%s]" % res)
- if "SUCCESS" in res:
- self.correctCaptcha()
- return True
- elif "ERROR:SESSION ERROR" in res:
- self.retry()
-
- 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 4309eefac..000000000
--- a/module/plugins/hoster/BoltsharingCom.py
+++ /dev/null
@@ -1,19 +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}'
- __config__ = []
-
- __description__ = """Boltsharing.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 9e3f28cfa..000000000
--- a/module/plugins/hoster/CatShareNet.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class CatShareNet(SimpleHoster):
- __name__ = "CatShareNet"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'http://(?:www\.)?catshare\.net/\w{16}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """CatShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com"),
- ("prOq", None),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- TEXT_ENCODING = True
-
- INFO_PATTERN = r'<title>(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<'
- OFFLINE_PATTERN = r'<div class="alert alert-error"'
-
- IP_BLOCKED_PATTERN = ur'>Nasz serwis wykrył ÅŒe Twój adres IP nie pochodzi z Polski.<'
- WAIT_PATTERN = r'var\scount\s=\s(\d+);'
-
- LINK_FREE_PATTERN = r'<form action="(.+?)" method="GET">'
- LINK_PREMIUM_PATTERN = r'<form action="(.+?)" method="GET">'
-
-
- def setup(self):
- self.multiDL = self.premium
- self.resumeDownload = True
-
-
- def checkErrors(self):
- m = re.search(self.IP_BLOCKED_PATTERN, self.html)
- if m:
- self.fail(_("Only connections from Polish IP address are allowed"))
-
- return super(CatShareNet, self).checkErrors()
-
-
- def handleFree(self, pyfile):
- recaptcha = ReCaptcha(self)
-
- response, challenge = recaptcha.challenge()
- self.html = self.load(pyfile.url,
- post={'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(CatShareNet)
diff --git a/module/plugins/hoster/CloudzerNet.py b/module/plugins/hoster/CloudzerNet.py
deleted file mode 100644
index d2d5e460a..000000000
--- a/module/plugins/hoster/CloudzerNet.py
+++ /dev/null
@@ -1,21 +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+'
- __config__ = []
-
- __description__ = """Cloudzer.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("gs", "I-_-I-_-I@web.de"),
- ("z00nx", "z00nx0@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(CloudzerNet)
diff --git a/module/plugins/hoster/CloudzillaTo.py b/module/plugins/hoster/CloudzillaTo.py
deleted file mode 100644
index 362a8d953..000000000
--- a/module/plugins/hoster/CloudzillaTo.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class CloudzillaTo(SimpleHoster):
- __name__ = "CloudzillaTo"
- __type__ = "hoster"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?cloudzilla\.to/share/file/(?P<ID>[\w^_]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Cloudzilla.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'title="(?P<N>.+?)">\1</span> <span class="size">\((?P<S>[\d.]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>File not found...<'
-
- PASSWORD_PATTERN = r'<div id="pwd_protected">'
-
-
- def checkErrors(self):
- m = re.search(self.PASSWORD_PATTERN, self.html)
- if m:
- self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
-
- if re.search(self.PASSWORD_PATTERN, self.html):
- self.retry(reason="Wrong password")
-
-
- def handleFree(self, pyfile):
- self.html = self.load("http://www.cloudzilla.to/generateticket/",
- post={'file_id': self.info['pattern']['ID'], 'key': self.getPassword()})
-
- ticket = dict(re.findall(r'<(.+?)>([^<>]+?)</', self.html))
-
- self.logDebug(ticket)
-
- if 'error' in ticket:
- if "File is password protected" in ticket['error']:
- self.retry(reason="Wrong password")
- else:
- self.fail(ticket['error'])
-
- if 'wait' in ticket:
- self.wait(ticket['wait'], int(ticket['wait']) > 5)
-
- self.link = "http://%(server)s/download/%(file_id)s/%(ticket_id)s" % {'server' : ticket['server'],
- 'file_id' : self.info['pattern']['ID'],
- 'ticket_id': ticket['ticket_id']}
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
-getInfo = create_getInfo(CloudzillaTo)
diff --git a/module/plugins/hoster/CramitIn.py b/module/plugins/hoster/CramitIn.py
deleted file mode 100644
index 44dac958d..000000000
--- a/module/plugins/hoster/CramitIn.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class CramitIn(XFSHoster):
- __name__ = "CramitIn"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'http://(?:www\.)?cramit\.in/\w{12}'
-
- __description__ = """Cramit.in hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<span class=t2>\s*(?P<N>.*?)</span>.*?<small>\s*\((?P<S>.*?)\)'
-
- LINK_PATTERN = r'href="(http://cramit\.in/file_download/.*?)"'
-
-
-getInfo = create_getInfo(CramitIn)
diff --git a/module/plugins/hoster/CrockoCom.py b/module/plugins/hoster/CrockoCom.py
deleted file mode 100644
index 1d7468520..000000000
--- a/module/plugins/hoster/CrockoCom.py
+++ /dev/null
@@ -1,67 +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.19"
-
- __pattern__ = r'http://(?:www\.)?(crocko|easy-share)\.com/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Crocko hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<span class="fz24">Download:\s*<strong>(?P<N>.*)'
- 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_PATTERN = r"u='(/file_contents/captcha/\w+)';\s*w='(\d+)';"
-
- FORM_PATTERN = r'<form method="post" action="(.+?)">(.*?)</form>'
- FORM_INPUT_PATTERN = r'<input[^>]* name="?([^" ]+)"? value="?([^" ]+)"?.*?>'
-
- NAME_REPLACEMENTS = [(r'<.*?>', '')]
-
-
- def handleFree(self, pyfile):
- if "You need Premium membership to download this file." in self.html:
- self.fail(_("You need Premium membership to download this file"))
-
- for _i in xrange(5):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- url, wait_time = 'http://crocko.com' + m.group(1), int(m.group(2))
- self.wait(wait_time)
- self.html = self.load(url)
- else:
- break
-
- m = re.search(self.FORM_PATTERN, self.html, re.S)
- if m is None:
- self.error(_("FORM_PATTERN not found"))
-
- action, form = m.groups()
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
- self.download(action, post=inputs)
-
- if self.checkDownload({"captcha": recaptcha.KEY_AJAX_PATTERN}):
- 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 d85a27282..000000000
--- a/module/plugins/hoster/CyberlockerCh.py
+++ /dev/null
@@ -1,19 +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+'
- __config__ = []
-
- __description__ = """Cyberlocker.ch hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "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 8f72f2148..000000000
--- a/module/plugins/hoster/CzshareCom.py
+++ /dev/null
@@ -1,162 +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.99"
-
- __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download\.php\?).+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """CZshare.com hoster plugin, now Sdilej.cz"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<div class="tab" id="parameters">\s*<p>\s*Cel. n.zev: <a href=.*?>(?P<N>[^<]+)</a>'
- SIZE_PATTERN = r'<div class="tab" id="category">(?:\s*<p>[^\n]*</p>)*\s*Velikost:\s*(?P<S>[\d .,]+)(?P<U>[\w^_]+)\s*</div>'
- OFFLINE_PATTERN = r'<div class="header clearfix">\s*<h2 class="red">'
-
- SIZE_REPLACEMENTS = [(' ', '')]
- URL_REPLACEMENTS = [(r'http://[^/]*/download.php\?.*?id=(\w+).*', r'http://sdilej.cz/\1/x/')]
-
- 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>([\d .,]+)(\w+)</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, 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(e)
-
- return True
-
-
- def handlePremium(self, pyfile):
- # parse download link
- try:
- form = re.search(self.PREMIUM_FORM_PATTERN, self.html, re.S).group(1)
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- except Exception, e:
- self.logError(e)
- self.resetAccount()
-
- # download the file, destination is determined by pyLoad
- self.download("http://sdilej.cz/profi_down.php", post=inputs, disposition=True)
-
-
- def handleFree(self, pyfile):
- # get free url
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m is None:
- self.error(_("FREE_URL_PATTERN not found"))
-
- 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, 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.S).group(1)
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- pyfile.size = int(inputs['size'])
-
- except Exception, e:
- self.logError(e)
- self.error(_("Form"))
-
- # get and decrypt captcha
- captcha_url = 'http://sdilej.cz/captcha.php'
- for _i in xrange(5):
- inputs['captchastring2'] = self.decryptCaptcha(captcha_url)
- self.html = self.load(parsed_url, 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.error(_("Download URL not found"))
-
- self.link = "http://%s/download.php?%s" % (m.group(1), m.group(2))
-
- self.wait()
-
-
- def checkFile(self, rules={}):
- # check download
- check = self.checkDownload({
- "temp offline" : 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" : "<li>ZadanÜ ověřovací kód nesouhlasí!</li>"
- })
-
- if check == "temp offline":
- self.fail(_("File not available - try later"))
-
- elif check == "credit":
- self.resetAccount()
-
- elif check == "multi-dl":
- self.longWait(5 * 60, 12)
-
- elif check == "captcha":
- self.invalidCaptcha()
- self.retry()
-
- return super(CzshareCom, self).checkFile(rules)
-
-
-getInfo = create_getInfo(CzshareCom)
diff --git a/module/plugins/hoster/DailymotionCom.py b/module/plugins/hoster/DailymotionCom.py
deleted file mode 100644
index c6939e5e5..000000000
--- a/module/plugins/hoster/DailymotionCom.py
+++ /dev/null
@@ -1,125 +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 = []
- regex = re.compile(DailymotionCom.__pattern__)
- apiurl = "https://api.dailymotion.com/video/%s"
- request = {"fields": "access_error,status,title"}
-
- for url in urls:
- id = regex.match(url).group('ID')
- html = getURL(apiurl % id, get=request)
- info = json_loads(html)
-
- name = info['title'] + ".mp4" if "title" in info else 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.20"
-
- __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"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def setup(self):
- self.resumeDownload = True
- 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 [item for item in enumerate(streams)][::-1]:
- 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()
-
- self.download(self.getLink(streams, quality))
diff --git a/module/plugins/hoster/DataHu.py b/module/plugins/hoster/DataHu.py
deleted file mode 100644
index 955c94437..000000000
--- a/module/plugins/hoster/DataHu.py
+++ /dev/null
@@ -1,35 +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.03"
-
- __pattern__ = r'http://(?:www\.)?data\.hu/get/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Data.hu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("crash", None),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- INFO_PATTERN = ur'<title>(?P<N>.*) \((?P<S>[^)]+)\) let\xf6lt\xe9se</title>'
- OFFLINE_PATTERN = ur'Az adott f\xe1jl nem l\xe9tezik'
- LINK_FREE_PATTERN = r'<div class="download_box_button"><a href="(.+?)">'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = self.premium
-
-
-getInfo = create_getInfo(DataHu)
diff --git a/module/plugins/hoster/DataportCz.py b/module/plugins/hoster/DataportCz.py
deleted file mode 100644
index ad514f5eb..000000000
--- a/module/plugins/hoster/DataportCz.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DataportCz(SimpleHoster):
- __name__ = "DataportCz"
- __type__ = "hoster"
- __version__ = "0.41"
-
- __pattern__ = r'http://(?:www\.)?dataport\.cz/file/(.+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Dataport.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<span itemprop="name">(?P<N>[^<]+)</span>'
- SIZE_PATTERN = r'<td class="fil">Velikost</td>\s*<td>(?P<S>[^<]+)</td>'
- OFFLINE_PATTERN = r'<h2>Soubor nebyl nalezen</h2>'
-
- CAPTCHA_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, pyfile):
- captchas = {"1": "jkeG", "2": "hMJQ", "3": "vmEK", "4": "ePQM", "5": "blBd"}
-
- for _i in xrange(60):
- action, inputs = self.parseHtmlForm('free_download_form')
- self.logDebug(action, inputs)
- if not action or not inputs:
- self.error(_("free_download_form"))
-
- if "captchaId" in inputs and inputs['captchaId'] in captchas:
- inputs['captchaCode'] = captchas[inputs['captchaId']]
- else:
- self.error(_("captcha"))
-
- 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.error(_("invalid captcha"))
-
- elif check == "slot":
- self.logDebug("No free slots - wait 60s and retry")
- self.wait(60, False)
- self.html = self.load(pyfile.url, decode=True)
- continue
-
- else:
- break
-
-
-getInfo = create_getInfo(DataportCz)
diff --git a/module/plugins/hoster/DateiTo.py b/module/plugins/hoster/DateiTo.py
deleted file mode 100644
index 71a9eb4bf..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.07"
-
- __pattern__ = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Datei.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'Dateiname:</td>\s*<td colspan="2"><strong>(?P<N>.*?)</'
- 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... <'
-
- WAIT_PATTERN = r'countdown\({seconds: (\d+)'
- MULTIDL_PATTERN = r'>Du lÀdst bereits eine Datei herunter<'
-
- DATA_PATTERN = r'url: "(.*?)", data: "(.*?)",'
-
-
- def handleFree(self, pyfile):
- url = 'http://datei.to/ajax/download.php'
- data = {'P': 'I', 'ID': self.info['pattern']['ID']}
- recaptcha = ReCaptcha(self)
-
- for _i 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.error(_("data"))
- url = 'http://datei.to/' + m.group(1)
- data = dict(x.split('=') for x in m.group(2).split('&'))
-
- if url.endswith('recaptcha.php'):
- data['recaptcha_response_field'], data['recaptcha_challenge_field'] = recaptcha.challenge()
- else:
- self.fail(_("Too bad..."))
-
- self.link = self.html
-
-
- def checkErrors(self):
- m = re.search(self.MULTIDL_PATTERN, self.html)
- if m:
- m = re.search(self.WAIT_PATTERN, self.html)
- wait_time = int(m.group(1)) if m else 30
-
- errmsg = self.info['error'] = _("Parallel downloads")
- self.retry(wait_time=wait_time, reason=errmsg)
-
- self.info.pop('error', None)
-
-
- 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, False)
-
-
-getInfo = create_getInfo(DateiTo)
diff --git a/module/plugins/hoster/DdlstorageCom.py b/module/plugins/hoster/DdlstorageCom.py
deleted file mode 100644
index d67b4f30f..000000000
--- a/module/plugins/hoster/DdlstorageCom.py
+++ /dev/null
@@ -1,20 +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+'
- __config__ = []
-
- __description__ = """DDLStorage.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "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 4f64215fa..000000000
--- a/module/plugins/hoster/DebridItaliaCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class DebridItaliaCom(MultiHoster):
- __name__ = "DebridItaliaCom"
- __type__ = "hoster"
- __version__ = "0.17"
-
- __pattern__ = r'https?://(?:www\.|s\d+\.)?debriditalia\.com/dl/\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Debriditalia.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [("https://", "http://")]
-
-
- def handlePremium(self, pyfile):
- self.html = self.load("http://www.debriditalia.com/api.php",
- get={'generate': "on", 'link': pyfile.url, 'p': self.getPassword()})
-
- if "ERROR:" not in self.html:
- self.link = self.html.strip()
- else:
- self.info['error'] = re.search(r'ERROR:(.*)', self.html).group(1).strip()
-
- self.html = self.load("http://debriditalia.com/linkgen2.php",
- post={'xjxfun' : "convertiLink",
- 'xjxargs[]': "S<![CDATA[%s]]>" % pyfile.url,
- 'xjxargs[]': "S%s" % self.getPassword()})
- try:
- self.link = re.search(r'<a href="(.+?)"', self.html).group(1)
- except AttributeError:
- pass
-
-
-getInfo = create_getInfo(DebridItaliaCom)
diff --git a/module/plugins/hoster/DepositfilesCom.py b/module/plugins/hoster/DepositfilesCom.py
deleted file mode 100644
index 8559bbc36..000000000
--- a/module/plugins/hoster/DepositfilesCom.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-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.55"
-
- __pattern__ = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Depositfiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<script type="text/javascript">eval\( unescape\(\'(?P<N>.*?)\''
- SIZE_PATTERN = r': <b>(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</b>'
- OFFLINE_PATTERN = r'<span class="html_download_api-not_exists"></span>'
-
- NAME_REPLACEMENTS = [(r'\%u([0-9A-Fa-f]{4})', lambda m: unichr(int(m.group(1), 16))),
- (r'.*<b title="(?P<N>.+?)".*', "\g<N>")]
- URL_REPLACEMENTS = [(__pattern__ + ".*", "https://dfiles.eu/files/\g<ID>")]
-
- COOKIES = [("dfiles.eu", "lang_current", "en")]
-
- WAIT_PATTERN = r'(?:download_waiter_remain">|html_download_api-limit_interval">|>Please wait|>Try in).+'
- ERROR_PATTER = r'File is checked, please try again in a minute'
-
- LINK_FREE_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"'
- LINK_PREMIUM_PATTERN = r'class="repeat"><a href="(.+?)"'
- LINK_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"'
-
-
- def handleFree(self, pyfile):
- self.html = self.load(pyfile.url, post={'gateway_result': "1"})
-
- self.checkErrors()
-
- 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'])
-
- self.checkErrors()
-
- recaptcha = ReCaptcha(self)
- captcha_key = recaptcha.detect_key()
- if captcha_key is None:
- return
-
- self.html = self.load("https://dfiles.eu/get_file.php", get=params)
-
- if '<input type=button value="Continue" onclick="check_recaptcha' in self.html:
- params['response'], params['challenge'] = recaptcha.challenge(captcha_key)
- self.html = self.load("https://dfiles.eu/get_file.php", get=params)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = urllib.unquote(m.group(1))
-
-
- def handlePremium(self, pyfile):
- 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.LINK_PREMIUM_PATTERN, self.html)
- mirror = re.search(self.LINK_MIRROR_PATTERN, self.html)
-
- if link:
- self.link = link.group(1)
-
- elif mirror:
- self.link = mirror.group(1)
-
-
-getInfo = create_getInfo(DepositfilesCom)
diff --git a/module/plugins/hoster/DevhostSt.py b/module/plugins/hoster/DevhostSt.py
deleted file mode 100644
index e7a23f6b1..000000000
--- a/module/plugins/hoster/DevhostSt.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://d-h.st/mM8
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DevhostSt(SimpleHoster):
- __name__ = "DevhostSt"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?d-h\.st/(?!users/)\w{3}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """d-h.st hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<span title="(?P<N>.*?)"'
- SIZE_PATTERN = r'</span> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<br'
- HASHSUM_PATTERN = r'>(?P<T>.*?) Sum</span>: &nbsp;(?P<H>.*?)<br'
-
- OFFLINE_PATTERN = r'>File Not Found'
- LINK_FREE_PATTERN = r'var product_download_url= \'(.+?)\''
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
-getInfo = create_getInfo(DevhostSt)
diff --git a/module/plugins/hoster/DlFreeFr.py b/module/plugins/hoster/DlFreeFr.py
deleted file mode 100644
index 72d15852c..000000000
--- a/module/plugins/hoster/DlFreeFr.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.network.Browser import Browser
-from module.network.CookieJar import CookieJar
-from module.plugins.internal.CaptchaService import AdYouLike
-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 DlFreeFr(SimpleHoster):
- __name__ = "DlFreeFr"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'http://(?:www\.)?dl\.free\.fr/(\w+|getfile\.pl\?file=/\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Dl.free.fr hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("the-razer", "daniel_ AT gmx DOT net"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("Toilal", "toilal.dev@gmail.com")]
-
-
- NAME_PATTERN = r'Fichier:</td>\s*<td.*?>(?P<N>[^>]*)</td>'
- SIZE_PATTERN = r'Taille:</td>\s*<td.*?>(?P<S>[\d.,]+\w)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.resumeDownload = True
- self.multiDL = 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):
- pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
- valid_url = pyfile.url
- headers = self.load(valid_url, just_header=True)
-
- 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(pyfile)
- else:
- # Direct access to requested file for users using free.fr as Internet Service Provider.
- self.link = valid_url
-
- elif headers.get('code') == 404:
- self.offline()
-
- else:
- self.fail(_("Invalid return code: ") + str(headers.get('code')))
-
-
- def handleFree(self, pyfile):
- action, inputs = self.parseHtmlForm('action="getfile.pl"')
-
- adyoulike = AdYouLike(self)
- response, challenge = adyoulike.challenge()
- inputs.update(response)
-
- 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"))
-
- self.link = headers.get("location")
-
- self.req.setCookieJar(cj)
- 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/DodanePl.py b/module/plugins/hoster/DodanePl.py
deleted file mode 100644
index 96f87a21c..000000000
--- a/module/plugins/hoster/DodanePl.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class DodanePl(DeadHoster):
- __name__ = "DodanePl"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?dodane\.pl/file/\d+'
- __config__ = []
-
- __description__ = """Dodane.pl hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
-getInfo = create_getInfo(DodanePl)
diff --git a/module/plugins/hoster/DropboxCom.py b/module/plugins/hoster/DropboxCom.py
deleted file mode 100644
index eec968f5a..000000000
--- a/module/plugins/hoster/DropboxCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DropboxCom(SimpleHoster):
- __name__ = "DropboxCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'https?://(?:www\.)?dropbox\.com/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Dropbox.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<title>Dropbox - (?P<N>.+?)<'
- SIZE_PATTERN = r'&nbsp;&middot;&nbsp; (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'<title>Dropbox - (404|Shared link error)<'
-
- COOKIES = [("dropbox.com", "lang", "en")]
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- self.download(pyfile.url, get={'dl': "1"})
-
-
-getInfo = create_getInfo(DropboxCom)
diff --git a/module/plugins/hoster/DuploadOrg.py b/module/plugins/hoster/DuploadOrg.py
deleted file mode 100644
index 542240bab..000000000
--- a/module/plugins/hoster/DuploadOrg.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class DuploadOrg(DeadHoster):
- __name__ = "DuploadOrg"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?dupload\.org/\w{12}'
- __config__ = []
-
- __description__ = """Dupload.grg hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(DuploadOrg)
diff --git a/module/plugins/hoster/EasybytezCom.py b/module/plugins/hoster/EasybytezCom.py
deleted file mode 100644
index 693910c1b..000000000
--- a/module/plugins/hoster/EasybytezCom.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class EasybytezCom(XFSHoster):
- __name__ = "EasybytezCom"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://(?:www\.)?easybytez\.com/\w{12}'
-
- __description__ = """Easybytez.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- OFFLINE_PATTERN = r'>File not available'
-
- LINK_PATTERN = r'(http://(\w+\.(easybytez|easyload|ezbytez|zingload)\.(com|to)|\d+\.\d+\.\d+\.\d+)/files/\d+/\w+/.+?)["\'<]'
-
-
-getInfo = create_getInfo(EasybytezCom)
diff --git a/module/plugins/hoster/EdiskCz.py b/module/plugins/hoster/EdiskCz.py
deleted file mode 100644
index b8485001e..000000000
--- a/module/plugins/hoster/EdiskCz.py
+++ /dev/null
@@ -1,57 +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.23"
-
- __pattern__ = r'http://(?:www\.)?edisk\.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Edisk.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<span class="fl" title="(?P<N>.+?)">\s*.*?\((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</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_FREE_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.error(_("ACTION_PATTERN not found"))
- 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_FREE_PATTERN, url):
- self.fail(_("Unexpected server response"))
-
- self.link = url
-
-
-getInfo = create_getInfo(EdiskCz)
diff --git a/module/plugins/hoster/EgoFilesCom.py b/module/plugins/hoster/EgoFilesCom.py
deleted file mode 100644
index 46d0fac6a..000000000
--- a/module/plugins/hoster/EgoFilesCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class EgoFilesCom(DeadHoster):
- __name__ = "EgoFilesCom"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'https?://(?:www\.)?egofiles\.com/\w+'
- __config__ = []
-
- __description__ = """Egofiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(EgoFilesCom)
diff --git a/module/plugins/hoster/EnteruploadCom.py b/module/plugins/hoster/EnteruploadCom.py
deleted file mode 100644
index b2306ce69..000000000
--- a/module/plugins/hoster/EnteruploadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class EnteruploadCom(DeadHoster):
- __name__ = "EnteruploadCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?enterupload\.com/\w+'
- __config__ = []
-
- __description__ = """EnterUpload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(EnteruploadCom)
diff --git a/module/plugins/hoster/EpicShareNet.py b/module/plugins/hoster/EpicShareNet.py
deleted file mode 100644
index e2a44b172..000000000
--- a/module/plugins/hoster/EpicShareNet.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class EpicShareNet(DeadHoster):
- __name__ = "EpicShareNet"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?epicshare\.net/\w{12}'
- __config__ = []
-
- __description__ = """EpicShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(EpicShareNet)
diff --git a/module/plugins/hoster/EuroshareEu.py b/module/plugins/hoster/EuroshareEu.py
deleted file mode 100644
index 842e0cb87..000000000
--- a/module/plugins/hoster/EuroshareEu.py
+++ /dev/null
@@ -1,68 +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.28"
-
- __pattern__ = r'http://(?:www\.)?euroshare\.(eu|sk|cz|hu|pl)/file/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Euroshare.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- 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!'
-
- LINK_FREE_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/"'
-
- URL_REPLACEMENTS = [(r"(http://[^/]*\.)(sk|cz|hu|pl)/", r"\1eu/")]
-
-
- def handlePremium(self, pyfile):
- if self.ERR_NOT_LOGGED_IN_PATTERN in self.html:
- self.account.relogin(self.user)
- self.retry(reason=_("User not logged in"))
-
- self.link = 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, pyfile):
- if re.search(self.ERR_PARDL_PATTERN, self.html):
- self.longWait(5 * 60, 12)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = "http://euroshare.eu%s" % m.group(1)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({"multi-dl": re.compile(self.ERR_PARDL_PATTERN)})
- self.longWait(5 * 60, 12)
-
- return super(EuroshareEu, self).checkFile(rules)
-
-
-getInfo = create_getInfo(EuroshareEu)
diff --git a/module/plugins/hoster/ExashareCom.py b/module/plugins/hoster/ExashareCom.py
deleted file mode 100644
index 536c09b6d..000000000
--- a/module/plugins/hoster/ExashareCom.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class ExashareCom(XFSHoster):
- __name__ = "ExashareCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?exashare\.com/\w{12}'
-
- __description__ = """Exashare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'>(?P<NAME>.+?)<small>\( (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- LINK_FREE_PATTERN = r'file: "(.+?)"'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = self.premium
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
- else:
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(ExashareCom)
diff --git a/module/plugins/hoster/ExtabitCom.py b/module/plugins/hoster/ExtabitCom.py
deleted file mode 100644
index f3b4d28be..000000000
--- a/module/plugins/hoster/ExtabitCom.py
+++ /dev/null
@@ -1,78 +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, secondsToMidnight
-
-
-class ExtabitCom(SimpleHoster):
- __name__ = "ExtabitCom"
- __type__ = "hoster"
- __version__ = "0.65"
-
- __pattern__ = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Extabit.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<th>File:</th>\s*<td class="col-fileinfo">\s*<div title="(?P<N>.+?)">'
- 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_FREE_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/\w+/.*?)[\'"]'
-
-
- def handleFree(self, pyfile):
- 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.info['pattern']['ID']
-
- m = re.search(r'recaptcha/api/challenge\?k=(\w+)', self.html)
- if m:
- recaptcha = ReCaptcha(self)
- captcha_key = m.group(1)
-
- for _i in xrange(5):
- get_data = {"type": "recaptcha"}
- get_data['capture'], get_data['challenge'] = recaptcha.challenge(captcha_key)
- res = json_loads(self.load("http://extabit.com/file/%s/" % fileID, get=get_data))
- if "ok" in res:
- self.correctCaptcha()
- break
- else:
- self.invalidCaptcha()
- else:
- self.fail(_("Invalid captcha"))
- else:
- self.error(_("Captcha"))
-
- if not "href" in res:
- self.error(_("Bad JSON response"))
-
- self.html = self.load("http://extabit.com/file/%s%s" % (fileID, res['href']))
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(ExtabitCom)
diff --git a/module/plugins/hoster/FastixRu.py b/module/plugins/hoster/FastixRu.py
deleted file mode 100644
index cc50f4229..000000000
--- a/module/plugins/hoster/FastixRu.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class FastixRu(MultiHoster):
- __name__ = "FastixRu"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?fastix\.(ru|it)/file/\w{24}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Fastix multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Massimo Rosamilia", "max@spiritix.eu")]
-
-
- def setup(self):
- self.chunkLimit = 3
-
-
- def handlePremium(self, pyfile):
- api_key = self.account.getAccountData(self.user)
- api_key = api_key['api']
-
- self.html = self.load("http://fastix.ru/api_v2/",
- get={'apikey': api_key, 'sub': "getdirectlink", 'link': pyfile.url})
-
- data = json_loads(self.html)
-
- self.logDebug("Json data", data)
-
- if "error\":true" in self.html:
- self.offline()
- else:
- self.link = data['downloadlink']
-
-
-getInfo = create_getInfo(FastixRu)
diff --git a/module/plugins/hoster/FastshareCz.py b/module/plugins/hoster/FastshareCz.py
deleted file mode 100644
index 330a6e3b9..000000000
--- a/module/plugins/hoster/FastshareCz.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FastshareCz(SimpleHoster):
- __name__ = "FastshareCz"
- __type__ = "hoster"
- __version__ = "0.29"
-
- __pattern__ = r'http://(?:www\.)?fastshare\.cz/\d+/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FastShare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
- URL_REPLACEMENTS = [("#.*", "")]
-
- COOKIES = [("fastshare.cz", "lang", "en")]
-
- NAME_PATTERN = r'<h3 class="section_title">(?P<N>.+?)<'
- SIZE_PATTERN = r'>Size\s*:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)'
-
- LINK_FREE_PATTERN = r'>Enter the code\s*:</em>\s*<span><img src="(.+?)"'
- LINK_PREMIUM_PATTERN = r'(http://\w+\.fastshare\.cz/download\.php\?id=\d+&)'
-
- SLOT_ERROR = "> 100% of FREE slots are full"
- CREDIT_ERROR = " credit for "
-
-
- def checkErrors(self):
- if self.SLOT_ERROR in self.html:
- errmsg = self.info['error'] = _("No free slots")
- self.retry(12, 60, errmsg)
-
- if self.CREDIT_ERROR in self.html:
- errmsg = self.info['error'] = _("Not enough traffic left")
- self.logWarning(errmsg)
- self.resetAccount()
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m:
- action, captcha_src = m.groups()
- else:
- self.error(_("FREE_URL_PATTERN not found"))
-
- baseurl = "http://www.fastshare.cz"
- captcha = self.decryptCaptcha(urlparse.urljoin(baseurl, captcha_src))
- self.download(urlparse.urljoin(baseurl, action), post={'code': captcha, 'btn.x': 77, 'btn.y': 18})
-
-
- def checkFile(self, rules={}):
- check = self.checkDownload({
- 'paralell-dl' : re.compile(r"<title>FastShare.cz</title>|<script>alert\('Pres FREE muzete stahovat jen jeden soubor najednou.'\)"),
- 'wrong captcha': re.compile(r'Download for FREE'),
- 'credit' : re.compile(self.CREDIT_ERROR)
- })
-
- if check == "paralell-dl":
- self.retry(6, 10 * 60, _("Paralell download"))
-
- elif check == "wrong captcha":
- self.retry(max_tries=5, reason=_("Wrong captcha"))
-
- elif check == "credit":
- self.resetAccount()
-
- return super(FastshareCz, self).checkFile(rules)
-
-
-getInfo = create_getInfo(FastshareCz)
diff --git a/module/plugins/hoster/FileApeCom.py b/module/plugins/hoster/FileApeCom.py
deleted file mode 100644
index 314f069f9..000000000
--- a/module/plugins/hoster/FileApeCom.py
+++ /dev/null
@@ -1,19 +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+'
- __config__ = []
-
- __description__ = """FileApe.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("espes", None)]
-
-
-getInfo = create_getInfo(FileApeCom)
diff --git a/module/plugins/hoster/FileSharkPl.py b/module/plugins/hoster/FileSharkPl.py
deleted file mode 100644
index de030be9c..000000000
--- a/module/plugins/hoster/FileSharkPl.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FileSharkPl(SimpleHoster):
- __name__ = "FileSharkPl"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?fileshark\.pl/pobierz/\d+/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FileShark.pl hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", None),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<h2 class="name-file">(?P<N>.+)</h2>'
- SIZE_PATTERN = r'<p class="size-file">(.*?)<strong>(?P<S>\d+\.?\d*)\s(?P<U>\w+)</strong></p>'
- OFFLINE_PATTERN = r'(P|p)lik zosta. (usuni.ty|przeniesiony)'
-
- LINK_FREE_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-free">'
- LINK_PREMIUM_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-premium">'
-
- WAIT_PATTERN = r'var timeToDownload = (\d+);'
- ERROR_PATTERN = r'<p class="lead text-center alert alert-warning">(.*?)</p>'
- IP_ERROR_PATTERN = r'Strona jest dost.pna wy..cznie dla u.ytkownik.w znajduj.cych si. na terenie Polski'
- SLOT_ERROR_PATTERN = r'Osi.gni.to maksymaln. liczb. .ci.ganych jednocze.nie plik.w\.'
-
- CAPTCHA_PATTERN = r'<img src="data:image/jpeg;base64,(.*?)" title="captcha"'
- TOKEN_PATTERN = r'name="form\[_token\]" value="(.*?)" />'
-
-
- def setup(self):
- self.resumeDownload = True
-
- if self.premium:
- self.multiDL = True
- self.limitDL = 20
- else:
- self.multiDL = False
-
-
- def checkErrors(self):
- # check if file is now available for download (-> file name can be found in html body)
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- errmsg = self.info['error'] = _("Another download already run")
- self.retry(15, int(m.group(1)), errmsg)
-
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- alert = m.group(1)
-
- if re.match(self.IP_ERROR_PATTERN, alert):
- self.fail(_("Only connections from Polish IP are allowed"))
-
- elif re.match(self.SLOT_ERROR_PATTERN, alert):
- errmsg = self.info['error'] = _("No free download slots available")
- self.logWarning(errmsg)
- self.retry(10, 30 * 60, _("Still no free download slots available"))
-
- else:
- self.info['error'] = alert
- self.retry(10, 10 * 60, _("Try again later"))
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download url not found"))
-
- link = urlparse.urljoin("http://fileshark.pl", m.group(1))
-
- self.html = self.load(link)
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- seconds = int(m.group(1))
- self.logDebug("Wait %s seconds" % seconds)
- self.wait(seconds)
-
- action, inputs = self.parseHtmlForm('action=""')
-
- m = re.search(self.TOKEN_PATTERN, self.html)
- if m is None:
- self.retry(reason=_("Captcha form not found"))
-
- inputs['form[_token]'] = m.group(1)
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.retry(reason=_("Captcha image not found"))
-
- tmp_load = self.load
- self.load = self._decode64 #: work-around: injects decode64 inside decryptCaptcha
-
- inputs['form[captcha]'] = self.decryptCaptcha(m.group(1), imgtype='jpeg')
- inputs['form[start]'] = ""
-
- self.load = tmp_load
-
- self.download(link, post=inputs, disposition=True)
-
-
- def _decode64(self, data, *args, **kwargs):
- return data.decode('base64')
-
-
-getInfo = create_getInfo(FileSharkPl)
diff --git a/module/plugins/hoster/FileStoreTo.py b/module/plugins/hoster/FileStoreTo.py
deleted file mode 100644
index 3262b73ad..000000000
--- a/module/plugins/hoster/FileStoreTo.py
+++ /dev/null
@@ -1,38 +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.05"
-
- __pattern__ = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FileStore.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- INFO_PATTERN = r'File: <span.*?>(?P<N>.+?)<.*>Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<'
- TEMP_OFFLINE_PATTERN = r'>Der Download ist nicht bereit !<'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- self.wait(10)
- self.link = self.load("http://filestore.to/ajax/download.php",
- get={'D': re.search(r'"D=(\w+)', self.html).group(1)})
-
-
-getInfo = create_getInfo(FileStoreTo)
diff --git a/module/plugins/hoster/FilebeerInfo.py b/module/plugins/hoster/FilebeerInfo.py
deleted file mode 100644
index 77ee367a7..000000000
--- a/module/plugins/hoster/FilebeerInfo.py
+++ /dev/null
@@ -1,19 +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+)'
- __config__ = []
-
- __description__ = """Filebeer.info plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 38d2972a1..000000000
--- a/module/plugins/hoster/FilecloudIo.py
+++ /dev/null
@@ -1,126 +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.08"
-
- __pattern__ = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filecloud.io hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- LOGIN_ACCOUNT = True
-
- NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?)&nbsp;&nbsp;<'
- SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}'
- 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\.(.*?);'
-
- RECAPTCHA_PATTERN = r'var __recaptcha_public\s*=\s*\'(.+?)\';'
-
- LINK_FREE_PATTERN = r'"(http://s\d+\.filecloud\.io/%s/\d+/.*?)"'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- data = {"ukey": self.info['pattern']['ID']}
-
- m = re.search(self.AB1_PATTERN, self.html)
- if m is None:
- self.error(_("__AB1"))
- data['__ab1'] = m.group(1)
-
- recaptcha = ReCaptcha(self)
-
- m = re.search(self.RECAPTCHA_PATTERN, self.html)
- captcha_key = m.group(1) if m else recaptcha.detect_key()
-
- if captcha_key is None:
- self.error(_("ReCaptcha key not found"))
-
- response, challenge = recaptcha.challenge(captcha_key)
- self.account.form_data = {"recaptcha_challenge_field": challenge,
- "recaptcha_response_field" : response}
- self.account.relogin(self.user)
- self.retry(2)
-
- json_url = "http://filecloud.io/download-request.json"
- res = self.load(json_url, post=data)
- self.logDebug(res)
- res = json_loads(res)
-
- if "error" in res and res['error']:
- self.fail(res)
-
- self.logDebug(res)
- if res['captcha']:
- data['ctype'] = "recaptcha"
-
- for _i in xrange(5):
- data['recaptcha_response'], data['recaptcha_challenge'] = recaptcha.challenge(captcha_key)
-
- json_url = "http://filecloud.io/download-request.json"
- res = self.load(json_url, post=data)
- self.logDebug(res)
- res = json_loads(res)
-
- if "retry" in res and res['retry']:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail(_("Incorrect captcha"))
-
- if res['dl']:
- self.html = self.load('http://filecloud.io/download.html')
-
- m = re.search(self.LINK_FREE_PATTERN % self.info['pattern']['ID'], self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- if "size" in self.info and self.info['size']:
- self.check_data = {"size": int(self.info['size'])}
-
- self.link = m.group(1)
- else:
- self.fail(_("Unexpected server response"))
-
-
- def handlePremium(self, pyfile):
- akey = self.account.getAccountData(self.user)['akey']
- ukey = self.info['pattern']['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.link = rep['download_url']
- 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 ea1a38b7a..000000000
--- a/module/plugins/hoster/FilefactoryCom.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-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
- yield parseFileInfo(FilefactoryCom, url, getURL(url))
-
-
-class FilefactoryCom(SimpleHoster):
- __name__ = "FilefactoryCom"
- __type__ = "hoster"
- __version__ = "0.55"
-
- __pattern__ = r'https?://(?:www\.)?filefactory\.com/(file|trafficshare/\w+)/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filefactory.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- 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'
- OFFLINE_PATTERN = r'<h2>File Removed</h2>|This file is no longer available|Invalid Download Link'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'"([^"]+filefactory\.com/get.+?)"'
-
- WAIT_PATTERN = r'<div id="countdown_clock" data-delay="(\d+)">'
- PREMIUM_ONLY_PATTERN = r'>Premium Account Required'
-
- COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
-
-
- def handleFree(self, pyfile):
- 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(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
-
- self.link = m.group(1)
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.wait(m.group(1))
-
-
- def checkFile(self, rules={}):
- 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.error(_("Unknown error"))
-
- return super(FilefactoryCom, self).checkFile(rules)
-
-
- def handlePremium(self, pyfile):
- self.link = self.directLink(self.load(pyfile.url, just_header=True))
-
- if not self.link:
- html = self.load(pyfile.url)
- m = re.search(self.LINK_PREMIUM_PATTERN, html)
- if m:
- self.link = m.group(1)
- else:
- self.error(_("Premium download link not found"))
diff --git a/module/plugins/hoster/FilejungleCom.py b/module/plugins/hoster/FilejungleCom.py
deleted file mode 100644
index 236603c6e..000000000
--- a/module/plugins/hoster/FilejungleCom.py
+++ /dev/null
@@ -1,29 +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"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 306953b84..000000000
--- a/module/plugins/hoster/FileomCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://fileom.com/gycaytyzdw3g/random.bin.html
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class FileomCom(XFSHoster):
- __name__ = "FileomCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?fileom\.com/\w{12}'
-
- __description__ = """Fileom.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'Filename: <span>(?P<N>.+?)<'
- SIZE_PATTERN = r'File Size: <span class="size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- LINK_PATTERN = r'var url2 = \'(.+?)\';'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = self.premium
-
-
-getInfo = create_getInfo(FileomCom)
diff --git a/module/plugins/hoster/FilepostCom.py b/module/plugins/hoster/FilepostCom.py
deleted file mode 100644
index 2a9e3dc26..000000000
--- a/module/plugins/hoster/FilepostCom.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-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.33"
-
- __pattern__ = r'https?://(?:www\.)?(?:filepost\.com/files|fp\.io)/(?P<ID>[^/]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filepost.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<input type="text" id="url" value=\'<a href.*?>(?P<N>[^>]+?) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</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_PATTERN = r'Captcha.init\({\s*key:\s*\'(.+?)\''
- FLP_TOKEN_PATTERN = r'set_store_options\({token: \'(.+?)\''
-
-
- def handleFree(self, pyfile):
- m = re.search(self.FLP_TOKEN_PATTERN, self.html)
- if m is None:
- self.error(_("Token"))
- flp_token = m.group(1)
-
- m = re.search(self.RECAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("Captcha key"))
- captcha_key = m.group(1)
-
- # Get wait time
- get_dict = {'SID': self.req.cj.getCookie('SID'), 'JsHttpRequest': str(int(time.time() * 10000)) + '-xml'}
- post_dict = {'action': 'set_download', 'token': flp_token, 'code': self.info['pattern']['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": self.info['pattern']['ID'], "file_pass": ''}
-
- if 'var is_pass_exists = true;' in self.html:
- # Solve password
- password = self.getPassword()
-
- if password:
- self.logInfo(_("Password protected link, trying ") + file_pass)
-
- get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
- post_dict['file_pass'] = file_pass
-
- self.link = self.getJsonResponse(get_dict, post_dict, 'link')
-
- if not self.link:
- self.fail(_("Incorrect password"))
- else:
- self.fail(_("No password found"))
-
- else:
- # Solve recaptcha
- recaptcha = ReCaptcha(self)
-
- for i in xrange(5):
- get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
- if i:
- post_dict['recaptcha_response_field'], post_dict['recaptcha_challenge_field'] = recaptcha.challenge(
- captcha_key)
- self.logDebug(u"RECAPTCHA: %s : %s : %s" % (
- captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field']))
-
- self.link = self.getJsonResponse(get_dict, post_dict, 'link')
-
- else:
- self.fail(_("Invalid captcha"))
-
-
- def getJsonResponse(self, get_dict, post_dict, field):
- res = json_loads(self.load('https://filepost.com/files/get/', get=get_dict, post=post_dict))
-
- self.logDebug(res)
-
- if not 'js' in res:
- self.error(_("JSON %s 1") % field)
-
- # i changed js_answer to res['js'] since js_answer is nowhere set.
- # i don't know the JSON-HTTP specs in detail, but the previous author
- # accessed res['js']['error'] as well as js_answer['error'].
- # see the two lines commented out with "# ~?".
- if 'error' in res['js']:
-
- if res['js']['error'] == 'download_delay':
- self.retry(wait_time=res['js']['params']['next_download'])
- # ~? self.retry(wait_time=js_answer['params']['next_download'])
-
- elif 'Wrong file password' in res['js']['error'] \
- or 'You entered a wrong CAPTCHA code' in res['js']['error'] \
- or 'CAPTCHA Code nicht korrekt' in res['js']['error']:
- return None
-
- elif 'CAPTCHA' in res['js']['error']:
- self.logDebug("Error response is unknown, but mentions CAPTCHA")
- return None
-
- else:
- self.fail(res['js']['error'])
-
- if not 'answer' in res['js'] or not field in res['js']['answer']:
- self.error(_("JSON %s 2") % field)
-
- return res['js']['answer'][field]
-
-
-getInfo = create_getInfo(FilepostCom)
diff --git a/module/plugins/hoster/FilepupNet.py b/module/plugins/hoster/FilepupNet.py
deleted file mode 100644
index 874fde3c8..000000000
--- a/module/plugins/hoster/FilepupNet.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www.filepup.net/files/k5w4ZVoF1410184283.html
-# http://www.filepup.net/files/R4GBq9XH1410186553.html
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilepupNet(SimpleHoster):
- __name__ = "FilepupNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?filepup\.net/files/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filepup.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'>(?P<N>.+?)</h1>'
- SIZE_PATTERN = r'class="fa fa-archive"></i> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'>This file has been deleted'
-
- LINK_FREE_PATTERN = r'(http://www\.filepup\.net/get/.+?)\''
-
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download link not found"))
-
- dl_link = m.group(1)
- self.download(dl_link, post={'task': "download"})
-
-
-getInfo = create_getInfo(FilepupNet)
diff --git a/module/plugins/hoster/FilerNet.py b/module/plugins/hoster/FilerNet.py
deleted file mode 100644
index 156392c79..000000000
--- a/module/plugins/hoster/FilerNet.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://filer.net/get/ivgf5ztw53et3ogd
-# http://filer.net/get/hgo14gzcng3scbvv
-
-import pycurl
-import re
-import urlparse
-
-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.19"
-
- __pattern__ = r'https?://(?:www\.)?filer\.net/get/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Filer.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- 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'
-
- WAIT_PATTERN = r'musst du <span id="time">(\d+)'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'href="([^"]+)">Get download</a>'
-
-
- def handleFree(self, pyfile):
- inputs = self.parseHtmlForm(input_names={'token': re.compile(r'.+')})[1]
- if 'token' not in inputs:
- self.error(_("Unable to detect token"))
-
- self.html = self.load(pyfile.url, post={'token': inputs['token']}, decode=True)
-
- inputs = self.parseHtmlForm(input_names={'hash': re.compile(r'.+')})[1]
- if 'hash' not in inputs:
- self.error(_("Unable to detect hash"))
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
-
- #@NOTE: Work-around for v0.4.9 just_header issue
- #@TODO: Check for v0.4.10
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.load(pyfile.url, post={'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response,
- 'hash' : inputs['hash']})
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
- if 'location' in self.req.http.header.lower():
- self.link = re.search(r'location: (\S+)', self.req.http.header, re.I).group(1)
- self.correctCaptcha()
- else:
- self.invalidCaptcha()
-
-
-getInfo = create_getInfo(FilerNet)
diff --git a/module/plugins/hoster/FilerioCom.py b/module/plugins/hoster/FilerioCom.py
deleted file mode 100644
index c6ebbd444..000000000
--- a/module/plugins/hoster/FilerioCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class FilerioCom(XFSHoster):
- __name__ = "FilerioCom"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'http://(?:www\.)?(filerio\.(in|com)|filekeen\.com)/\w{12}'
-
- __description__ = """FileRio.in hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- URL_REPLACEMENTS = [(r'filekeen\.com', "filerio.in")]
-
- OFFLINE_PATTERN = r'>&quot;File Not Found|File has been removed'
-
-
-getInfo = create_getInfo(FilerioCom)
diff --git a/module/plugins/hoster/FilesMailRu.py b/module/plugins/hoster/FilesMailRu.py
deleted file mode 100644
index 7bd099282..000000000
--- a/module/plugins/hoster/FilesMailRu.py
+++ /dev/null
@@ -1,105 +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:
- html = getURL(url)
- if r'<div class="errorMessage mb10">' in html:
- result.append((url, 0, 1, url))
- elif r'Page cannot be displayed' in html:
- 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, html).group(0).split(', event)">')[1].split('</a>')[0]
- result.append((file_name, 0, 2, url))
- except Exception:
- 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.32"
-
- __pattern__ = r'http://(?:www\.)?files\.mail\.ru/.+'
-
- __description__ = """Files.mail.ru hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("oZiRiz", "ich@oziriz.de")]
-
-
- def setup(self):
- self.multiDL = bool(self.account)
-
-
- 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 4bca2eb59..000000000
--- a/module/plugins/hoster/FileserveCom.py
+++ /dev/null
@@ -1,216 +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.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import secondsToMidnight
-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.S):
- 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.54"
-
- __pattern__ = r'http://(?:www\.)?fileserve\.com/file/(?P<ID>[^/]+)'
-
- __description__ = """Fileserve.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("mkaay", "mkaay@mkaay.de"),
- ("Paul King", None),
- ("zoidberg", "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=\'(.+?)\''
- 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.error(_("Unknown server response"))
-
- # show download link
- res = self.load(self.url, post={"downloadLink": "show"}, decode=True)
- self.logDebug("Show downloadLink response: %s" % res)
- if "fail" in res:
- self.error(_("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):
- res = self.load(self.url, post={"downloadLink": "wait"}, decode=True)
- self.logDebug("Wait response: %s" % res[:80])
-
- if "fail" in res:
- self.fail(_("Failed getting wait time"))
-
- if self.__name__ == "FilejungleCom":
- m = re.search(r'"waitTime":(\d+)', res)
- if m is None:
- self.fail(_("Cannot get wait time"))
- wait_time = int(m.group(1))
- else:
- wait_time = int(res) + 3
-
- self.setWait(wait_time)
- self.wait()
-
-
- def doCaptcha(self):
- captcha_key = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group(1)
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge(captcha_key)
- res = json_loads(self.load(self.URLS[2],
- post={'recaptcha_challenge_field' : challenge,
- 'recaptcha_response_field' : response,
- 'recaptcha_shortencode_field': self.file_id}))
- if not res['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
- res = 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 res:
- res = json_loads(res)
- if res['error_code'] == "302":
- premium_url = res['next']
- elif res['error_code'] in ["305", "500"]:
- self.tempOffline()
- elif res['error_code'] in ["403", "605"]:
- self.resetAccount()
- elif res['error_code'] in ["606", "607", "608"]:
- self.offline()
- else:
- self.logError(res['error_code'], res['error_message'])
-
- self.download(premium_url or self.pyfile.url)
-
- if not premium_url and self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)}):
- 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 c636375f5..000000000
--- a/module/plugins/hoster/FileshareInUa.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FileshareInUa(DeadHoster):
- __name__ = "FileshareInUa"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?fileshare\.in\.ua/\w{7}'
- __config__ = []
-
- __description__ = """Fileshare.in.ua hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fwannmacher", "felipe@warhammerproject.com")]
-
-
-getInfo = create_getInfo(FileshareInUa)
diff --git a/module/plugins/hoster/FilesonicCom.py b/module/plugins/hoster/FilesonicCom.py
deleted file mode 100644
index 16b4dee14..000000000
--- a/module/plugins/hoster/FilesonicCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FilesonicCom(DeadHoster):
- __name__ = "FilesonicCom"
- __type__ = "hoster"
- __version__ = "0.35"
-
- __pattern__ = r'http://(?:www\.)?filesonic\.com/file/\w+'
- __config__ = []
-
- __description__ = """Filesonic.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("paulking", None)]
-
-
-getInfo = create_getInfo(FilesonicCom)
diff --git a/module/plugins/hoster/FilezyNet.py b/module/plugins/hoster/FilezyNet.py
deleted file mode 100644
index 2a6ba2722..000000000
--- a/module/plugins/hoster/FilezyNet.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FilezyNet(DeadHoster):
- __name__ = "FilezyNet"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?filezy\.net/\w{12}'
- __config__ = []
-
- __description__ = """Filezy.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
-getInfo = create_getInfo(FilezyNet)
diff --git a/module/plugins/hoster/FiredriveCom.py b/module/plugins/hoster/FiredriveCom.py
deleted file mode 100644
index 612b9ceca..000000000
--- a/module/plugins/hoster/FiredriveCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FiredriveCom(DeadHoster):
- __name__ = "FiredriveCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
- __config__ = []
-
- __description__ = """Firedrive.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(FiredriveCom)
diff --git a/module/plugins/hoster/FlyFilesNet.py b/module/plugins/hoster/FlyFilesNet.py
deleted file mode 100644
index 689eb3c66..000000000
--- a/module/plugins/hoster/FlyFilesNet.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster
-
-
-class FlyFilesNet(SimpleHoster):
- __name__ = "FlyFilesNet"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?flyfiles\.net/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FlyFiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
- SESSION_PATTERN = r'flyfiles\.net/(.*)/.*'
- NAME_PATTERN = r'flyfiles\.net/.*/(.*)'
-
-
- def process(self, pyfile):
- name = re.search(self.NAME_PATTERN, pyfile.url).group(1)
- pyfile.name = urllib.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})
- 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()
-
- self.link = parsed_url.replace('#downlink|', '')
diff --git a/module/plugins/hoster/FourSharedCom.py b/module/plugins/hoster/FourSharedCom.py
deleted file mode 100644
index 79eb1fb83..000000000
--- a/module/plugins/hoster/FourSharedCom.py
+++ /dev/null
@@ -1,63 +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.31"
-
- __pattern__ = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """4Shared.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<meta name="title" content="(?P<N>.+?)"'
- SIZE_PATTERN = r'<span title="Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)">'
- OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted.'
-
- NAME_REPLACEMENTS = [(r"&#(\d+).", lambda m: unichr(int(m.group(1))))]
- SIZE_REPLACEMENTS = [(",", "")]
-
- DIRECT_LINK = False
- LOGIN_ACCOUNT = True
-
- LINK_FREE_PATTERN = r'name="d3link" value="(.*?)"'
- LINK_BTN_PATTERN = r'id="btnLink" href="(.*?)"'
-
- ID_PATTERN = r'name="d3fid" value="(.*?)"'
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_BTN_PATTERN, self.html)
- if m:
- link = m.group(1)
- else:
- link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', pyfile.url)
-
- self.html = self.load(link)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download link"))
-
- self.link = m.group(1)
-
- try:
- m = re.search(self.ID_PATTERN, self.html)
- res = self.load('http://www.4shared.com/web/d2/getFreeDownloadLimitInfo?fileId=%s' % m.group(1))
- self.logDebug(res)
- except Exception:
- pass
-
- self.wait(20)
-
-
-getInfo = create_getInfo(FourSharedCom)
diff --git a/module/plugins/hoster/FreakshareCom.py b/module/plugins/hoster/FreakshareCom.py
deleted file mode 100644
index 6a595e75b..000000000
--- a/module/plugins/hoster/FreakshareCom.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import secondsToMidnight
-
-
-class FreakshareCom(Hoster):
- __name__ = "FreakshareCom"
- __type__ = "hoster"
- __version__ = "0.40"
-
- __pattern__ = r'http://(?:www\.)?freakshare\.(net|com)/files/\S*?/'
-
- __description__ = """Freakshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sitacuisses", "sitacuisses@yahoo.de"),
- ("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("Toilal", "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.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:
- m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html)
- if m:
- file_name = m.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:
- m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html)
- if m:
- units = float(m.group(1).replace(",", ""))
- pow = {'KB': 1, 'MB': 2, 'GB': 3}[m.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))
- 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):
- 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
-
- to_sort = re.findall(r"<input\stype=\".*?\"\svalue=\"(\S*?)\".*?name=\"(\S*?)\"\s.*?\/>", herewego)
- request_options = dict((n, v) for (v, n) in to_sort)
-
- challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=(\w+)", 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 1cca24c3b..000000000
--- a/module/plugins/hoster/FreeWayMe.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class FreeWayMe(MultiHoster):
- __name__ = "FreeWayMe"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'https://(?:www\.)?free-way\.me/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FreeWayMe multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Nicolas Giese", "james@free-way.me")]
-
-
- def setup(self):
- self.resumeDownload = False
- self.multiDL = self.premium
- self.chunkLimit = 1
-
-
- def handlePremium(self, pyfile):
- user, data = self.account.selectAccount()
-
- for _i in xrange(5):
- # try it five times
- header = self.load("https://www.free-way.me/load.php",
- get={'multiget': 7,
- 'url' : pyfile.url,
- 'user' : user,
- 'pw' : self.account.getAccountData(user)['password'],
- 'json' : ""},
- just_header=True)
-
- if 'location' in header:
- headers = self.load(header['location'], just_header=True)
- if headers['code'] == 500:
- # error on 2nd stage
- self.logError(_("Error [stage2]"))
- else:
- # seems to work..
- self.download(header['location'])
- break
- else:
- # error page first stage
- self.logError(_("Error [stage1]"))
-
- #@TODO: handle errors
-
-
-getInfo = create_getInfo(FreeWayMe)
diff --git a/module/plugins/hoster/FreevideoCz.py b/module/plugins/hoster/FreevideoCz.py
deleted file mode 100644
index f47c07c7e..000000000
--- a/module/plugins/hoster/FreevideoCz.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FreevideoCz(DeadHoster):
- __name__ = "FreevideoCz"
- __type__ = "hoster"
- __version__ = "0.30"
-
- __pattern__ = r'http://(?:www\.)?freevideo\.cz/vase-videa/.+'
- __config__ = []
-
- __description__ = """Freevideo.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 50128db10..000000000
--- a/module/plugins/hoster/FshareVn.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urlparse
-
-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)
-
- yield parseFileInfo(FshareVn, url, html)
-
-
-def doubleDecode(m):
- return m.group(1).decode('raw_unicode_escape')
-
-
-class FshareVn(SimpleHoster):
- __name__ = "FshareVn"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?fshare\.vn/file/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """FshareVn hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<p>(?P<N>[^<]+)<\\/p>[\\trn\s]*<p>(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)<\\/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>'
-
- NAME_REPLACEMENTS = [("(.*)", doubleDecode)]
-
- LINK_FREE_PATTERN = r'action="(http://download.*?)[#"]'
- WAIT_PATTERN = ur'Lượt tải xuống kế tiếp là:\s*(.*?)\s*<'
-
-
- def preload(self):
- self.html = self.load("http://www.fshare.vn/check_link.php",
- post={'action': "check_link", 'arrlinks': pyfile.url},
- decode=True)
-
- if isinstance(self.TEXT_ENCODING, basestring):
- self.html = unicode(self.html, self.TEXT_ENCODING)
-
-
- def handleFree(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
-
- self.checkErrors()
-
- action, inputs = self.parseHtmlForm('frm_download')
- url = urlparse.urljoin(pyfile.url, action)
-
- if not inputs:
- self.error(_("No FORM"))
-
- elif 'link_file_pwd_dl' in inputs:
- password = self.getPassword()
-
- if password:
- self.logInfo(_("Password protected link, trying ") + password)
- inputs['link_file_pwd_dl'] = password
- self.html = self.load(url, post=inputs, decode=True)
-
- if 'name="link_file_pwd_dl"' in self.html:
- self.fail(_("Incorrect password"))
- else:
- self.fail(_("No password found"))
-
- else:
- self.html = self.load(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_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = m.group(1)
- self.wait()
-
-
- 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 = time.mktime.time(time.strptime.time(m.group(1), "%d/%m/%Y %H:%M"))
- self.wait(wait_until - time.mktime.time(time.gmtime.time()) - 7 * 60 * 60, True)
- self.retry()
- elif '<ul class="message-error">' in self.html:
- msg = "Unknown error occured or wait time not parsed"
- self.logError(msg)
- self.retry(30, 2 * 60, msg)
-
- self.info.pop('error', None)
diff --git a/module/plugins/hoster/Ftp.py b/module/plugins/hoster/Ftp.py
deleted file mode 100644
index 5258a1c27..000000000
--- a/module/plugins/hoster/Ftp.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import urllib
-import urlparse
-
-from module.plugins.Hoster import Hoster
-
-
-class Ftp(Hoster):
- __name__ = "Ftp"
- __type__ = "hoster"
- __version__ = "0.51"
-
- __pattern__ = r'(?:ftps?|sftp)://([\w.-]+(:[\w.-]+)?@)?[\w.-]+(:\d+)?/.+'
-
- __description__ = """Download from ftp directory"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.com"),
- ("mkaay", "mkaay@mkaay.de"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
-
- def process(self, pyfile):
- parsed_url = urlparse.urlparse(pyfile.url)
- netloc = parsed_url.netloc
-
- pyfile.name = parsed_url.path.rpartition('/')[2]
- try:
- pyfile.name = urllib.unquote(str(pyfile.name)).decode('utf8')
- except Exception:
- 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.getAccountInfo(netloc)['password'])
- else:
- pwd = self.getPassword()
- if ':' in pwd:
- self.req.addAuth(pwd)
-
- self.req.http.c.setopt(pycurl.NOBODY, 1)
-
- try:
- res = 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+)", res)
- 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.urlparse(pyfile.url).path.rpartition('/')[2])
- pyfile.url += '/'
- self.req.http.c.setopt(48, 1) # CURLOPT_DIRLISTONLY
- res = self.load(pyfile.url, decode=False)
- links = [pyfile.url + urllib.quote(x) for x in res.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 c68866f87..000000000
--- a/module/plugins/hoster/GamefrontCom.py
+++ /dev/null
@@ -1,90 +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/\w+'
-
- __description__ = """Gamefront.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fwannmacher", "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 = True
- 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(_("Plugin broken")
-
- return name.group(1)
-
-
- def _getLink(self):
- self.html2 = self.load("http://www.gamefront.com/" + re.search("(files/service/thankyou\\?id=\w+)",
- self.html).group(1))
- return re.search("<a href=\"(http://media\d+\.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 e4d0601fc..000000000
--- a/module/plugins/hoster/GigapetaCom.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import random
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class GigapetaCom(SimpleHoster):
- __name__ = "GigapetaCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?gigapeta\.com/dl/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """GigaPeta.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<img src=".*" alt="file" />-->\s*(?P<N>.*?)\s*</td>'
- SIZE_PATTERN = r'<th>\s*Size\s*</th>\s*<td>\s*(?P<S>.*?)\s*</td>'
- OFFLINE_PATTERN = r'<div id="page_error">'
-
- COOKIES = [("gigapeta.com", "lang", "us")]
-
-
- def handleFree(self, pyfile):
- captcha_key = str(random.randint(1, 100000000))
- captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
-
- for _i in xrange(5):
- self.checkErrors()
-
- captcha = self.decryptCaptcha(captcha_url)
- self.html = self.load(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:
- self.link = m.group(1).rstrip() #@TODO: Remove .rstrip() in 0.4.10
- 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(pycurl.FOLLOWLOCATION, 1)
-
-
- def checkErrors(self):
- if "All threads for IP" in self.html:
- self.logDebug("Your IP is already downloading a file")
- self.wait(5 * 60, True)
- self.retry()
-
- self.info.pop('error', None)
-
-
-getInfo = create_getInfo(GigapetaCom)
diff --git a/module/plugins/hoster/GooIm.py b/module/plugins/hoster/GooIm.py
deleted file mode 100644
index 4b27e6cc8..000000000
--- a/module/plugins/hoster/GooIm.py
+++ /dev/null
@@ -1,38 +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.04"
-
- __pattern__ = r'https?://(?:www\.)?goo\.im/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Goo.im hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- 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.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- self.wait(10)
- self.link = pyfile.url
-
-
-getInfo = create_getInfo(GooIm)
diff --git a/module/plugins/hoster/GoogledriveCom.py b/module/plugins/hoster/GoogledriveCom.py
deleted file mode 100644
index 09c54a8da..000000000
--- a/module/plugins/hoster/GoogledriveCom.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# https://drive.google.com/file/d/0B6RNTe4ygItBQm15RnJiTmMyckU/view?pli=1
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.utils import html_unescape
-
-
-class GoogledriveCom(SimpleHoster):
- __name__ = "GoogledriveCom"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'https?://(?:www\.)?drive\.google\.com/file/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Drive.google.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- DISPOSITION = False #: Remove in 0.4.10
-
- NAME_PATTERN = r'"og:title" content="(?P<N>.*?)">'
- OFFLINE_PATTERN = r'align="center"><p class="errorMessage"'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- try:
- link1 = re.search(r'"(https://docs.google.com/uc\?id.*?export=download)",',
- self.html.decode('unicode-escape')).group(1)
-
- except AttributeError:
- self.error(_("Hop #1 not found"))
-
- else:
- self.logDebug("Next hop: %s" % link1)
-
- self.html = self.load(link1).decode('unicode-escape')
-
- try:
- link2 = html_unescape(re.search(r'href="(/uc\?export=download.*?)">',
- self.html).group(1))
-
- except AttributeError:
- self.error(_("Hop #2 not found"))
-
- else:
- self.logDebug("Next hop: %s" % link2)
-
- link3 = self.load("https://docs.google.com" + link2, just_header=True)
- self.logDebug("DL-Link: %s" % link3['location'])
-
- self.link = link3['location']
-
-
-getInfo = create_getInfo(GoogledriveCom)
diff --git a/module/plugins/hoster/HellshareCz.py b/module/plugins/hoster/HellshareCz.py
deleted file mode 100644
index ac0043b37..000000000
--- a/module/plugins/hoster/HellshareCz.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class HellshareCz(SimpleHoster):
- __name__ = "HellshareCz"
- __type__ = "hoster"
- __version__ = "0.85"
-
- __pattern__ = r'http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Hellshare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- CHECK_TRAFFIC = True
- LOGIN_ACCOUNT = True
-
- NAME_PATTERN = r'<h1 id="filename"[^>]*>(?P<N>[^<]+)</h1>'
- SIZE_PATTERN = r'<strong id="FileSize_master">(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</strong>'
- OFFLINE_PATTERN = r'<h1>File not found.</h1>'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = bool(self.account)
- self.chunkLimit = 1
-
-
-getInfo = create_getInfo(HellshareCz)
diff --git a/module/plugins/hoster/HellspyCz.py b/module/plugins/hoster/HellspyCz.py
deleted file mode 100644
index 49fb9e829..000000000
--- a/module/plugins/hoster/HellspyCz.py
+++ /dev/null
@@ -1,19 +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+)'
- __config__ = []
-
- __description__ = """HellSpy.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(HellspyCz)
diff --git a/module/plugins/hoster/HostujeNet.py b/module/plugins/hoster/HostujeNet.py
deleted file mode 100644
index ec91e50b9..000000000
--- a/module/plugins/hoster/HostujeNet.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class HostujeNet(SimpleHoster):
- __name__ = "HostujeNet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?hostuje\.net/\w+'
-
- __description__ = """Hostuje.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("GammaC0de", None)]
-
-
- NAME_PATTERN = r'<input type="hidden" name="name" value="(?P<N>.+?)">'
- SIZE_PATTERN = r'<b>Rozmiar:</b> (?P<S>[\d.,]+) (?P<U>[\w^_]+)<br>'
- OFFLINE_PATTERN = ur'Podany plik nie został odnaleziony\.\.\.'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- m = re.search(r'<script src="([\w^_]+.php)"></script>', self.html)
- if m:
- jscript = self.load("http://hostuje.net/" + m.group(1))
- m = re.search(r"\('(\w+\.php\?i=\w+)'\);", jscript)
- if m:
- self.load("http://hostuje.net/" + m.group(1))
- else:
- self.error(_("unexpected javascript format"))
- else:
- self.error(_("script not found"))
-
- action, inputs = self.parseHtmlForm(pyfile.url.replace(".", "\.").replace( "?", "\?"))
- if not action:
- self.error(_("form not found"))
-
- self.download(action, post=inputs)
-
-
-getInfo = create_getInfo(HostujeNet)
diff --git a/module/plugins/hoster/HotfileCom.py b/module/plugins/hoster/HotfileCom.py
deleted file mode 100644
index 6c6a4fa89..000000000
--- a/module/plugins/hoster/HotfileCom.py
+++ /dev/null
@@ -1,22 +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+/\w+'
- __config__ = []
-
- __description__ = """Hotfile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sitacuisses", "sitacuisses@yhoo.de"),
- ("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("JoKoT3", "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 3fdcca1ba..000000000
--- a/module/plugins/hoster/HugefilesNet.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class HugefilesNet(XFSHoster):
- __name__ = "HugefilesNet"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?hugefiles\.net/\w{12}'
-
- __description__ = """Hugefiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- SIZE_PATTERN = r'File Size:</span>\s*<span.*?>(?P<S>[^<]+)</span></div>'
-
- FORM_INPUTS_MAP = {'ctype': re.compile(r'\d+')}
-
-
-getInfo = create_getInfo(HugefilesNet)
diff --git a/module/plugins/hoster/HundredEightyUploadCom.py b/module/plugins/hoster/HundredEightyUploadCom.py
deleted file mode 100644
index 2a35a008f..000000000
--- a/module/plugins/hoster/HundredEightyUploadCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class HundredEightyUploadCom(XFSHoster):
- __name__ = "HundredEightyUploadCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?180upload\.com/\w{12}'
-
- __description__ = """180upload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- OFFLINE_PATTERN = r'>File Not Found'
-
-
-getInfo = create_getInfo(HundredEightyUploadCom)
diff --git a/module/plugins/hoster/IFileWs.py b/module/plugins/hoster/IFileWs.py
deleted file mode 100644
index 7d2a76a60..000000000
--- a/module/plugins/hoster/IFileWs.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class IFileWs(DeadHoster):
- __name__ = "IFileWs"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?ifile\.ws/\w{12}'
- __config__ = []
-
- __description__ = """Ifile.ws hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
-getInfo = create_getInfo(IFileWs)
diff --git a/module/plugins/hoster/IcyFilesCom.py b/module/plugins/hoster/IcyFilesCom.py
deleted file mode 100644
index ca0f6441b..000000000
--- a/module/plugins/hoster/IcyFilesCom.py
+++ /dev/null
@@ -1,19 +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/(.+)'
- __config__ = []
-
- __description__ = """IcyFiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "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 f13065f86..000000000
--- a/module/plugins/hoster/IfileIt.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class IfileIt(DeadHoster):
- __name__ = "IfileIt"
- __type__ = "hoster"
- __version__ = "0.29"
-
- __pattern__ = r'^unmatchable$'
- __config__ = []
-
- __description__ = """Ifile.it"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(IfileIt)
diff --git a/module/plugins/hoster/IfolderRu.py b/module/plugins/hoster/IfolderRu.py
deleted file mode 100644
index 0f09731e4..000000000
--- a/module/plugins/hoster/IfolderRu.py
+++ /dev/null
@@ -1,67 +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.39"
-
- __pattern__ = r'http://(?:www)?(files\.)?(ifolder\.ru|metalarea\.org|rusfolder\.(com|net|ru))/(files/)?(?P<ID>\d+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Ifolder.ru hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- SIZE_REPLACEMENTS = [(u'Кб', 'KB'), (u'Мб', 'MB'), (u'Гб', 'GB')]
-
- NAME_PATTERN = ur'(?:<div><span>)?НазваМОе:(?:</span>)? <b>(?P<N>[^<]+)</b><(?:/div|br)>'
- SIZE_PATTERN = ur'(?:<div><span>)?РазЌер:(?:</span>)? <b>(?P<S>[^<]+)</b><(?:/div|br)>'
- OFFLINE_PATTERN = ur'<p>Ѐайл МПЌер <b>.*?</b> (Ме МайЎеМ|уЎалеМ) !!!</p>'
-
- SESSION_ID_PATTERN = r'<input type="hidden" name="session" value="(.+?)"'
- INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag\.value = "(.+?)";\}'
- HIDDEN_INPUT_PATTERN = r'var v = .*?name=\'(.+?)\' value=\'1\''
-
- LINK_FREE_PATTERN = r'<a href="(.+?)" class="downloadbutton_files"'
-
- WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МеверМый кПЎ,<br>ввеЎОте еще раз</font><br>'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = bool(self.account)
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- url = "http://rusfolder.com/%s" % self.info['pattern']['ID']
- self.html = self.load("http://rusfolder.com/%s" % self.info['pattern']['ID'], decode=True)
- self.getFileInfo()
-
- session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups()
-
- captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id
- for _i in xrange(5):
- action, inputs = self.parseHtmlForm('id="download-step-one-form"')
- inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True)
- inputs['action'] = '1'
- self.logDebug(inputs)
-
- self.html = self.load(url, decode=True, post=inputs)
- if self.WRONG_CAPTCHA_PATTERN in self.html:
- self.invalidCaptcha()
- else:
- break
- else:
- self.fail(_("Invalid captcha"))
-
- self.link = re.search(self.LINK_FREE_PATTERN, self.html).group(1)
-
-
-getInfo = create_getInfo(IfolderRu)
-
diff --git a/module/plugins/hoster/JumbofilesCom.py b/module/plugins/hoster/JumbofilesCom.py
deleted file mode 100644
index a9bf14c6d..000000000
--- a/module/plugins/hoster/JumbofilesCom.py
+++ /dev/null
@@ -1,37 +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.03"
-
- __pattern__ = r'http://(?:www\.)?jumbofiles\.com/(?P<ID>\w{12})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """JumboFiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com")]
-
-
- INFO_PATTERN = r'<TR><TD>(?P<N>[^<]+?)\s*<small>\((?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'Not Found or Deleted / Disabled due to inactivity or DMCA'
- LINK_FREE_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- post_data = {"id": self.info['pattern']['ID'], "op": "download3", "rand": ""}
- html = self.load(self.pyfile.url, post=post_data, decode=True)
- self.link = re.search(self.LINK_FREE_PATTERN, html).group(1)
-
-
-getInfo = create_getInfo(JumbofilesCom)
diff --git a/module/plugins/hoster/JunocloudMe.py b/module/plugins/hoster/JunocloudMe.py
deleted file mode 100644
index 415d5e2d0..000000000
--- a/module/plugins/hoster/JunocloudMe.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class JunocloudMe(XFSHoster):
- __name__ = "JunocloudMe"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:\w+\.)?junocloud\.me/\w{12}'
-
- __description__ = """Junocloud.me hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- URL_REPLACEMENTS = [(r'//(www\.)?junocloud', "//dl3.junocloud")]
-
- OFFLINE_PATTERN = r'>No such file with this filename<'
- TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
-
-
-getInfo = create_getInfo(JunocloudMe)
diff --git a/module/plugins/hoster/Keep2ShareCc.py b/module/plugins/hoster/Keep2ShareCc.py
deleted file mode 100644
index fdae65096..000000000
--- a/module/plugins/hoster/Keep2ShareCc.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-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.21"
-
- __pattern__ = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Keep2Share.cc hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", "http://keep2s.cc/file/\g<ID>")]
-
- NAME_PATTERN = r'File: <span>(?P<N>.+)</span>'
- SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>'
-
- OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted|Error 404'
- TEMP_OFFLINE_PATTERN = r'Downloading blocked due to'
-
- LINK_FREE_PATTERN = r'"(.+?url.html\?file=.+?)"|window\.location\.href = \'(.+?)\';'
- LINK_PREMIUM_PATTERN = r'window\.location\.href = \'(.+?)\';'
-
- CAPTCHA_PATTERN = r'src="(/file/captcha\.html.+?)"'
-
- WAIT_PATTERN = r'Please wait ([\d:]+) to download this file'
- TEMP_ERROR_PATTERN = r'>\s*(Download count files exceed|Traffic limit exceed|Free account does not allow to download more than one file at the same time)'
- ERROR_PATTERN = r'>\s*(Free user can\'t download large files|You no can access to this file|This download available only for premium users|This is private file)'
-
-
- def checkErrors(self):
- m = re.search(self.TEMP_ERROR_PATTERN, self.html)
- if m:
- self.info['error'] = m.group(1)
- self.wantReconnect = True
- self.retry(wait_time=30 * 60, reason=m.group(0))
-
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- errmsg = self.info['error'] = m.group(1)
- self.error(errmsg)
-
- 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.wantReconnect = True
- self.retry(wait_time=wait_time, reason="Please wait to download this file")
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- self.fid = re.search(r'<input type="hidden" name="slow_id" value="(.+?)">', self.html).group(1)
- self.html = self.load(pyfile.url, post={'yt0': '', 'slow_id': self.fid})
-
- # self.logDebug(self.fid)
- # self.logDebug(pyfile.url)
-
- self.checkErrors()
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.handleCaptcha()
- self.wait(31)
- self.html = self.load(pyfile.url)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
-
- self.link = m.group(1)
-
-
- def handleCaptcha(self):
- post_data = {'free' : 1,
- 'freeDownloadRequest': 1,
- 'uniqueId' : self.fid,
- 'yt0' : ''}
-
- m = re.search(r'id="(captcha\-form)"', self.html)
- self.logDebug("captcha-form found %s" % m)
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- self.logDebug("CAPTCHA_PATTERN found %s" % m)
- if m:
- captcha_url = urlparse.urljoin("http://keep2s.cc/", m.group(1))
- post_data['CaptchaForm[code]'] = self.decryptCaptcha(captcha_url)
- else:
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
- post_data.update({'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- self.html = self.load(self.pyfile.url, post=post_data)
-
- if 'verification code is incorrect' not in self.html:
- self.correctCaptcha()
- else:
- self.invalidCaptcha()
-
-
-getInfo = create_getInfo(Keep2ShareCc)
diff --git a/module/plugins/hoster/KickloadCom.py b/module/plugins/hoster/KickloadCom.py
deleted file mode 100644
index df43b0fca..000000000
--- a/module/plugins/hoster/KickloadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class KickloadCom(DeadHoster):
- __name__ = "KickloadCom"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?kickload\.com/get/.+'
- __config__ = []
-
- __description__ = """Kickload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(KickloadCom)
diff --git a/module/plugins/hoster/KingfilesNet.py b/module/plugins/hoster/KingfilesNet.py
deleted file mode 100644
index 99f309d00..000000000
--- a/module/plugins/hoster/KingfilesNet.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class KingfilesNet(SimpleHoster):
- __name__ = "KingfilesNet"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'http://(?:www\.)?kingfiles\.net/(?P<ID>\w{12})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Kingfiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'name="fname" value="(?P<N>.+?)">'
- SIZE_PATTERN = r'>Size: .+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'>(File Not Found</b><br><br>|File Not Found</h2>)'
-
- RAND_ID_PATTERN = r'type=\"hidden\" name=\"rand\" value=\"(.+)\">'
-
- LINK_FREE_PATTERN = r'var download_url = \'(.+)\';'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- # Click the free user button
- post_data = {'op' : "download1",
- 'usr_login' : "",
- 'id' : self.info['pattern']['ID'],
- 'fname' : pyfile.name,
- 'referer' : "",
- 'method_free': "+"}
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- solvemedia = SolveMedia(self)
- response, challenge = solvemedia.challenge()
-
- # Make the downloadlink appear and load the file
- m = re.search(self.RAND_ID_PATTERN, self.html)
- if m is None:
- self.error(_("Random key not found"))
-
- rand = m.group(1)
- self.logDebug("rand = ", rand)
-
- post_data = {'op' : "download2",
- 'id' : self.info['pattern']['ID'],
- 'rand' : rand,
- 'referer' : pyfile.url,
- 'method_free' : "+",
- 'method_premium' : "",
- 'adcopy_response' : response,
- 'adcopy_challenge': challenge,
- 'down_direct' : "1"}
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download url not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(KingfilesNet)
diff --git a/module/plugins/hoster/LemUploadsCom.py b/module/plugins/hoster/LemUploadsCom.py
deleted file mode 100644
index c7aae3bf9..000000000
--- a/module/plugins/hoster/LemUploadsCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class LemUploadsCom(DeadHoster):
- __name__ = "LemUploadsCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?lemuploads\.com/\w{12}'
- __config__ = []
-
- __description__ = """LemUploads.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(LemUploadsCom)
diff --git a/module/plugins/hoster/LetitbitNet.py b/module/plugins/hoster/LetitbitNet.py
deleted file mode 100644
index 40d792e11..000000000
--- a/module/plugins/hoster/LetitbitNet.py
+++ /dev/null
@@ -1,136 +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
-import urlparse
-
-from module.common.json_layer import json_loads, json_dumps
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, secondsToMidnight
-
-
-def api_response(url):
- json_data = ["yw7XQy2v9", ["download/info", {"link": url}]]
- api_rep = getURL("http://api.letitbit.net/json",
- post={'r': json_dumps(json_data)})
- return json_loads(api_rep)
-
-
-def getInfo(urls):
- for url in urls:
- api_rep = api_response(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.30"
-
- __pattern__ = r'https?://(?:www\.)?(letitbit|shareflare)\.net/download/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Letitbit.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("z00nx", "z00nx0@gmail.com")]
-
-
- URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "letitbit.net")]
-
- SECONDS_PATTERN = r'seconds\s*=\s*(\d+);'
- CAPTCHA_CONTROL_FIELD = r'recaptcha_control_field\s=\s\'(.+?)\''
-
-
- def setup(self):
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- action, inputs = self.parseHtmlForm('id="ifree_form"')
- if not action:
- self.error(_("ifree_form"))
-
- pyfile.size = float(inputs['sssize'])
- self.logDebug(action, inputs)
- inputs['desc'] = ""
-
- self.html = self.load(urlparse.urljoin("http://letitbit.net/", action), post=inputs)
-
- 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)
-
- res = self.load("http://letitbit.net/ajax/download3.php", post=" ")
- if res != '1':
- self.error(_("Unknown response - ajax_check_url"))
-
- self.logDebug(res)
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
-
- post_data = {"recaptcha_challenge_field": challenge,
- "recaptcha_response_field": response,
- "recaptcha_control_field": recaptcha_control_field}
-
- self.logDebug("Post data to send", post_data)
-
- res = self.load("http://letitbit.net/ajax/check_recaptcha.php", post=post_data)
-
- self.logDebug(res)
-
- if not res:
- self.invalidCaptcha()
-
- if res == "error_free_download_blocked":
- self.logWarning(_("Daily limit reached"))
- self.wait(secondsToMidnight(gmt=2), True)
-
- if res == "error_wrong_captcha":
- self.invalidCaptcha()
- self.retry()
-
- elif res.startswith('['):
- urls = json_loads(res)
-
- elif res.startswith('http://'):
- urls = [res]
-
- else:
- self.error(_("Unknown response - captcha check"))
-
- self.link = urls[0]
-
-
- def handlePremium(self, pyfile):
- api_key = self.user
- premium_key = self.account.getAccountData(self.user)['password']
-
- json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": 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'])
-
- self.link = api_rep['data'][0][0]
diff --git a/module/plugins/hoster/LinksnappyCom.py b/module/plugins/hoster/LinksnappyCom.py
deleted file mode 100644
index 9c3af4ae3..000000000
--- a/module/plugins/hoster/LinksnappyCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class LinksnappyCom(MultiHoster):
- __name__ = "LinksnappyCom"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'https?://(?:[^/]+\.)?linksnappy\.com'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Linksnappy.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- SINGLE_CHUNK_HOSTERS = ["easybytez.com"]
-
-
- def handlePremium(self, pyfile):
- 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.error(_("Error converting the link"))
-
- pyfile.name = j['filename']
- self.link = j['generated']
-
- if host in self.SINGLE_CHUNK_HOSTERS:
- self.chunkLimit = 1
- else:
- self.setup()
-
-
- @staticmethod
- def _get_host(url):
- host = urlparse.urlsplit(url).netloc
- return re.search(r'[\w-]+\.\w+$', host).group(0)
-
-
-getInfo = create_getInfo(LinksnappyCom)
diff --git a/module/plugins/hoster/LoadTo.py b/module/plugins/hoster/LoadTo.py
deleted file mode 100644
index 2b4202051..000000000
--- a/module/plugins/hoster/LoadTo.py
+++ /dev/null
@@ -1,67 +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.22"
-
- __pattern__ = r'http://(?:www\.)?load\.to/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """ Load.to hoster plugin """
- __license__ = "GPLv3"
- __authors__ = [("halfman", "Pulpan3@gmail.com"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- NAME_PATTERN = r'<h1>(?P<N>.+)</h1>'
- SIZE_PATTERN = r'Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>Can\'t find file'
-
- LINK_FREE_PATTERN = r'<form method="post" action="(.+?)"'
- WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
-
- URL_REPLACEMENTS = [(r'(\w)$', r'\1/')]
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- # Search for Download URL
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = 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:
- solvemedia = SolveMedia(self)
- captcha_key = solvemedia.detect_key()
-
- if captcha_key:
- response, challenge = solvemedia.challenge(captcha_key)
- self.download(self.link,
- post={'adcopy_challenge': challenge,
- 'adcopy_response' : response,
- 'returnUrl' : pyfile.url})
-
-
-getInfo = create_getInfo(LoadTo)
diff --git a/module/plugins/hoster/LolabitsEs.py b/module/plugins/hoster/LolabitsEs.py
deleted file mode 100644
index 61df5f0bb..000000000
--- a/module/plugins/hoster/LolabitsEs.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*
-
-import HTMLParser
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class LolabitsEs(SimpleHoster):
- __name__ = "LolabitsEs"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?lolabits\.es/.+'
-
- __description__ = """Lolabits.es hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'Descargar: <b>(?P<N>.+?)<'
- SIZE_PATTERN = r'class="fileSize">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'Un usuario con este nombre no existe'
-
- FILEID_PATTERN = r'name="FileId" value="(\d+)"'
- TOKEN_PATTERN = r'name="__RequestVerificationToken" type="hidden" value="(.+?)"'
- LINK_PATTERN = r'"redirectUrl":"(.+?)"'
-
-
- def setup(self):
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- fileid = re.search(self.FILEID_PATTERN, self.html).group(1)
- self.logDebug("FileID: " + fileid)
-
- token = re.search(self.TOKEN_PATTERN, self.html).group(1)
- self.logDebug("Token: " + token)
-
- self.html = self.load("http://lolabits.es/action/License/Download",
- post={'fileId' : fileid,
- '__RequestVerificationToken' : token}).decode('unicode-escape')
-
- self.link = HTMLParser.HTMLParser().unescape(re.search(self.LINK_PATTERN, self.html).group(1))
-
-
-getInfo = create_getInfo(LolabitsEs)
diff --git a/module/plugins/hoster/LomafileCom.py b/module/plugins/hoster/LomafileCom.py
deleted file mode 100644
index 4dd092f45..000000000
--- a/module/plugins/hoster/LomafileCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class LomafileCom(DeadHoster):
- __name__ = "LomafileCom"
- __type__ = "hoster"
- __version__ = "0.52"
-
- __pattern__ = r'http://lomafile\.com/\w{12}'
- __config__ = []
-
- __description__ = """Lomafile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("nath_schwarz", "nathan.notwhite@gmail.com"),
- ("guidobelix", "guidobelix@hotmail.it")]
-
-
-getInfo = create_getInfo(LomafileCom)
diff --git a/module/plugins/hoster/LuckyShareNet.py b/module/plugins/hoster/LuckyShareNet.py
deleted file mode 100644
index 26af8153f..000000000
--- a/module/plugins/hoster/LuckyShareNet.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from 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.06"
-
- __pattern__ = r'https?://(?:www\.)?luckyshare\.net/(?P<ID>\d{10,})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """LuckyShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- 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'
-
-
- 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:
- seconds = int(m.group(1))
- self.logDebug("You have to wait %d seconds between free downloads" % seconds)
- self.retry(wait_time=seconds)
- else:
- self.error(_("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, pyfile):
- rep = self.load(r"http://luckyshare.net/download/request/type/time/file/" + self.info['pattern']['ID'], decode=True)
-
- self.logDebug("JSON: " + rep)
-
- json = self.parseJson(rep)
- self.wait(json['time'])
-
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge()
- 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.invalidCaptcha()
- else:
- self.error(_("Unable to get downlaod link"))
-
- if not json['link']:
- self.fail(_("No Download url retrieved/all captcha attempts failed"))
-
- self.link = 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 c022918ae..000000000
--- a/module/plugins/hoster/MediafireCom.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.CaptchaService import SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MediafireCom(SimpleHoster):
- __name__ = "MediafireCom"
- __type__ = "hoster"
- __version__ = "0.86"
-
- __pattern__ = r'https?://(?:www\.)?mediafire\.com/(file/|view/\??|download(\.php\?|/)|\?)\w{15}'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Mediafire.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>.+?)"/>'
- SIZE_PATTERN = r'<li>File size: <span>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- INFO_PATTERN = r'oFileSharePopup\.ald\(\'.*?\',\'(?P<N>.+?)\',\'(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)\',\'\',\'(?P<H>.+?)\'\)'
- OFFLINE_PATTERN = r'class="error_msg_title"'
-
- LINK_FREE_PATTERN = r'kNO = "(.+?)"'
-
- PASSWORD_PATTERN = r'<form name="form_password"'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- solvemedia = SolveMedia(self)
- captcha_key = solvemedia.detect_key()
-
- if captcha_key:
- response, challenge = solvemedia.challenge(captcha_key)
- self.html = self.load(pyfile.url,
- post={'adcopy_challenge': challenge,
- 'adcopy_response' : response},
- decode=True)
-
- if self.PASSWORD_PATTERN in self.html:
- password = self.getPassword()
-
- if not password:
- self.fail(_("No password found"))
- else:
- self.logInfo(_("Password protected link, trying: ") + password)
- self.html = self.load(self.link, post={'downloadp': password})
-
- if self.PASSWORD_PATTERN in self.html:
- self.fail(_("Incorrect password"))
-
- return super(MediafireCom, self).handleFree(pyfile)
-
-
-getInfo = create_getInfo(MediafireCom)
diff --git a/module/plugins/hoster/MegaCoNz.py b/module/plugins/hoster/MegaCoNz.py
deleted file mode 100644
index aa7755af4..000000000
--- a/module/plugins/hoster/MegaCoNz.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import array
-import os
-# import pycurl
-import random
-import re
-
-from base64 import standard_b64decode
-
-from Crypto.Cipher import AES
-from Crypto.Util import Counter
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.Hoster import Hoster
-from module.utils import decode, fs_decode, fs_encode
-
-
-############################ General errors ###################################
-# EINTERNAL (-1): An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred
-# EARGS (-2): You have passed invalid arguments to this command
-# EAGAIN (-3): (always at the request level) A temporary congestion or server malfunction prevented your request from being processed. No data was altered. Retry. Retries must be spaced with exponential backoff
-# ERATELIMIT (-4): You have exceeded your command weight per time quota. Please wait a few seconds, then try again (this should never happen in sane real-life applications)
-#
-############################ Upload errors ####################################
-# EFAILED (-5): The upload failed. Please restart it from scratch
-# ETOOMANY (-6): Too many concurrent IP addresses are accessing this upload target URL
-# ERANGE (-7): The upload file packet is out of range or not starting and ending on a chunk boundary
-# EEXPIRED (-8): The upload target URL you are trying to access has expired. Please request a fresh one
-#
-############################ Stream/System errors #############################
-# ENOENT (-9): Object (typically, node or user) not found
-# ECIRCULAR (-10): Circular linkage attempted
-# EACCESS (-11): Access violation (e.g., trying to write to a read-only share)
-# EEXIST (-12): Trying to create an object that already exists
-# EINCOMPLETE (-13): Trying to access an incomplete resource
-# EKEY (-14): A decryption operation failed (never returned by the API)
-# ESID (-15): Invalid or expired user session, please relogin
-# EBLOCKED (-16): User blocked
-# EOVERQUOTA (-17): Request over quota
-# ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later
-# ETOOMANYCONNECTIONS (-19): Too many connections on this resource
-# EWRITE (-20): Write failed
-# EREAD (-21): Read failed
-# EAPPKEY (-22): Invalid application key; request not processed
-
-
-class MegaCoNz(Hoster):
- __name__ = "MegaCoNz"
- __type__ = "hoster"
- __version__ = "0.26"
-
- __pattern__ = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#(?P<TYPE>N|)!(?P<ID>[\w^_]+)!(?P<KEY>[\w,-]+)'
-
- __description__ = """Mega.co.nz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "ranan@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- API_URL = "https://eu.api.mega.co.nz/cs"
- 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.array("I", self.b64_decode(key))
-
- k = array.array("I", (a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7]))
- iv = a[4:6] + array.array("I", (0, 0))
- meta_mac = a[6:8]
-
- return k, iv, meta_mac
-
-
- def api_response(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.random.randint(10 << 9, 10 ** 10)
-
- res = self.load(self.API_URL, get={'id': uid}, post=json_dumps([kwargs]))
- self.logDebug("Api Response: " + res)
- return json_loads(res)
-
-
- def decryptAttr(self, data, key):
- k, iv, meta_mac = self.getCipherKey(key)
- cbc = AES.new(k, AES.MODE_CBC, "\0" * 16)
- attr = decode(cbc.decrypt(self.b64_decode(data)))
-
- self.logDebug("Decrypted Attr: %s" % attr)
- if not attr.startswith("MEGA"):
- self.fail(_("Decryption failed"))
-
- # Data is padded, 0-bytes must be stripped
- return json_loads(re.search(r'{.+?}', attr).group(0))
-
-
- def decryptFile(self, key):
- """ Decrypts the file at lastDownload` """
-
- # upper 64 bit of counter start
- n = self.b64_decode(key)[16:24]
-
- # convert counter to long and shift bytes
- k, iv, meta_mac = self.getCipherKey(key)
- ctr = Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64)
- cipher = AES.new(k, AES.MODE_CTR, counter=ctr)
-
- self.pyfile.setStatus("decrypting")
- self.pyfile.setProgress(0)
-
- file_crypted = fs_encode(self.lastDownload)
- file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0]
-
- try:
- f = open(file_crypted, "rb")
- df = open(file_decrypted, "wb")
-
- except IOError, e:
- self.fail(e)
-
- chunk_size = 2 ** 15 # buffer size, 32k
- # file_mac = [0, 0, 0, 0] # calculate CBC-MAC for checksum
-
- chunks = os.path.getsize(file_crypted) / chunk_size + 1
- for i in xrange(chunks):
- buf = f.read(chunk_size)
- if not buf:
- break
-
- chunk = cipher.decrypt(buf)
- df.write(chunk)
-
- self.pyfile.setProgress(int((100.0 / chunks) * i))
-
- # chunk_mac = [iv[0], iv[1], iv[0], iv[1]]
- # for i in xrange(0, chunk_size, 16):
- # block = chunk[i:i+16]
- # if len(block) % 16:
- # block += '=' * (16 - (len(block) % 16))
- # block = array.array("I", block)
-
- # chunk_mac = [chunk_mac[0] ^ a_[0], chunk_mac[1] ^ block[1], chunk_mac[2] ^ block[2], chunk_mac[3] ^ block[3]]
- # chunk_mac = aes_cbc_encrypt_a32(chunk_mac, k)
-
- # file_mac = [file_mac[0] ^ chunk_mac[0], file_mac[1] ^ chunk_mac[1], file_mac[2] ^ chunk_mac[2], file_mac[3] ^ chunk_mac[3]]
- # file_mac = aes_cbc_encrypt_a32(file_mac, k)
-
- self.pyfile.setProgress(100)
-
- f.close()
- df.close()
-
- # if file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3] != meta_mac:
- # os.remove(file_decrypted)
- # self.fail(_("Checksum mismatch"))
-
- os.remove(file_crypted)
- self.lastDownload = fs_decode(file_decrypted)
-
-
- def checkError(self, code):
- ecode = abs(code)
-
- if ecode in (9, 16, 21):
- self.offline()
-
- elif ecode in (3, 13, 17, 18, 19):
- self.tempOffline()
-
- elif ecode in (1, 4, 6, 10, 15, 21):
- self.retry(5, 30, _("Error code: [%s]") % -ecode)
-
- else:
- self.fail(_("Error code: [%s]") % -ecode)
-
-
- def process(self, pyfile):
- pattern = re.match(self.__pattern__, pyfile.url).groupdict()
- id = pattern['ID']
- key = pattern['KEY']
- public = pattern['TYPE'] == ''
-
- self.logDebug("ID: %s" % id, "Key: %s" % key, "Type: %s" % ("public" if public else "node"))
-
- # g is for requesting a download url
- # this is similar to the calls in the mega js app, documentation is very bad
- if public:
- mega = self.api_response(a="g", g=1, p=id, ssl=1)[0]
- else:
- mega = self.api_response(a="g", g=1, n=id, ssl=1)[0]
-
- if isinstance(mega, int):
- self.checkError(mega)
- elif "e" in mega:
- self.checkError(mega['e'])
-
- attr = self.decryptAttr(mega['at'], key)
-
- pyfile.name = attr['n'] + self.FILE_SUFFIX
- pyfile.size = mega['s']
-
- # self.req.http.c.setopt(pycurl.SSL_CIPHER_LIST, "RC4-MD5:DEFAULT")
-
- self.download(mega['g'])
-
- self.decryptFile(key)
-
- # Everything is finished and final name can be set
- pyfile.name = attr['n']
diff --git a/module/plugins/hoster/MegaDebridEu.py b/module/plugins/hoster/MegaDebridEu.py
deleted file mode 100644
index 9c8cc2a90..000000000
--- a/module/plugins/hoster/MegaDebridEu.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class MegaDebridEu(MultiHoster):
- __name__ = "MegaDebridEu"
- __type__ = "hoster"
- __version__ = "0.47"
-
- __pattern__ = r'http://((?:www\d+\.|s\d+\.)?mega-debrid\.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[\w^_]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """mega-debrid.eu multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("D.Ducatel", "dducatel@je-geek.fr")]
-
-
- API_URL = "https://www.mega-debrid.eu/api.php"
-
-
- def api_load(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']})
- res = json_loads(jsonResponse)
-
- if res['response_code'] == "ok":
- self.token = res['token']
- return True
- else:
- return False
-
-
- def handlePremium(self, pyfile):
- """
- Debrid a link
- Return The debrided link if succeed or original link if fail
- """
- if not self.api_load():
- self.error("Unable to connect to remote API")
-
- jsonResponse = self.load(self.API_URL,
- get={'action': 'getLink', 'token': self.token},
- post={'link': pyfile.url})
-
- res = json_loads(jsonResponse)
- if res['response_code'] == "ok":
- self.link = res['debridLink'][1:-1]
-
-
-getInfo = create_getInfo(MegaDebridEu)
diff --git a/module/plugins/hoster/MegaFilesSe.py b/module/plugins/hoster/MegaFilesSe.py
deleted file mode 100644
index 795b74a78..000000000
--- a/module/plugins/hoster/MegaFilesSe.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegaFilesSe(DeadHoster):
- __name__ = "MegaFilesSe"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?megafiles\.se/\w{12}'
- __config__ = []
-
- __description__ = """MegaFiles.se hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(MegaFilesSe)
diff --git a/module/plugins/hoster/MegaRapidCz.py b/module/plugins/hoster/MegaRapidCz.py
deleted file mode 100644
index 59fb8251e..000000000
--- a/module/plugins/hoster/MegaRapidCz.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getRequest
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-
-
-def getInfo(urls):
- h = getRequest()
- h.c.setopt(pycurl.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)
- yield parseFileInfo(MegaRapidCz, url, html)
-
-
-class MegaRapidCz(SimpleHoster):
- __name__ = "MegaRapidCz"
- __type__ = "hoster"
- __version__ = "0.56"
-
- __pattern__ = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """MegaRapid.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("MikyWoW", "mikywow@seznam.cz"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<h1.*?><span.*?>(?:<a.*?>)?(?P<N>[^<]+)'
- SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong></td>'
- OFFLINE_PATTERN = ur'Nastala chyba 404|Soubor byl smazán'
-
- CHECK_TRAFFIC = True
-
- LINK_PREMIUM_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, pyfile):
- m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
- if m:
- self.link = m.group(1)
- else:
- if re.search(self.ERR_LOGIN_PATTERN, self.html):
- self.relogin(self.user)
- self.retry(wait_time=60, 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/MegaRapidoNet.py b/module/plugins/hoster/MegaRapidoNet.py
deleted file mode 100644
index a3b4c72ba..000000000
--- a/module/plugins/hoster/MegaRapidoNet.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-def random_with_N_digits(n):
- rand = "0."
- not_zero = 0
- for i in range(1, n + 1):
- r = random.randint(0, 9)
- if(r > 0):
- not_zero += 1
- rand += str(r)
-
- if not_zero > 0:
- return rand
- else:
- return random_with_N_digits(n)
-
-
-class MegaRapidoNet(MultiHoster):
- __name__ = "MegaRapidoNet"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?\w+\.megarapido\.net/\?file=\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """MegaRapido.net multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- LINK_PREMIUM_PATTERN = r'<\s*?a[^>]*?title\s*?=\s*?["\'].*?download["\'][^>]*?href=["\']([^"\']+)'
-
- ERROR_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']?alert-message error.*?>([^<]*)'
-
-
- def handlePremium(self, pyfile):
- self.html = self.load("http://megarapido.net/gerar.php",
- post={'rand' :random_with_N_digits(16),
- 'urllist' : pyfile.url,
- 'links' : pyfile.url,
- 'exibir' : "normal",
- 'usar' : "premium",
- 'user' : self.account.getAccountInfo(self.user).get('sid', None),
- 'autoreset': ""})
-
- if "desloga e loga novamente para gerar seus links" in self.html.lower():
- self.error("You have logged in at another place")
-
- return super(MegaRapidoNet, self).handlePremium(pyfile)
diff --git a/module/plugins/hoster/MegacrypterCom.py b/module/plugins/hoster/MegacrypterCom.py
deleted file mode 100644
index 10a2eb025..000000000
--- a/module/plugins/hoster/MegacrypterCom.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads, json_dumps
-
-from module.plugins.hoster.MegaCoNz import MegaCoNz
-
-
-class MegacrypterCom(MegaCoNz):
- __name__ = "MegacrypterCom"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'https?://\w{0,10}\.?megacrypter\.com/[\w!-]+'
-
- __description__ = """Megacrypter.com decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("GonzaloSR", "gonzalo@gonzalosr.com")]
-
-
- API_URL = "http://megacrypter.com/api"
- FILE_SUFFIX = ".crypted"
-
-
- def api_response(self, **kwargs):
- """ Dispatch a call to the api, see megacrypter.com/api_doc """
- self.logDebug("JSON request: " + json_dumps(kwargs))
- res = self.load(self.API_URL, post=json_dumps(kwargs))
- self.logDebug("API Response: " + res)
- return json_loads(res)
-
-
- def process(self, pyfile):
- # match is guaranteed because plugin was chosen to handle url
- node = re.match(self.__pattern__, pyfile.url).group(0)
-
- # get Mega.co.nz link info
- info = self.api_response(link=node, m="info")
-
- # get crypted file URL
- dl = self.api_response(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 1460ac868..000000000
--- a/module/plugins/hoster/MegareleaseOrg.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegareleaseOrg(DeadHoster):
- __name__ = "MegareleaseOrg"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?megarelease\.org/\w{12}'
- __config__ = []
-
- __description__ = """Megarelease.org hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("derek3x", "derek3x@vmail.me"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(MegareleaseOrg)
diff --git a/module/plugins/hoster/MegasharesCom.py b/module/plugins/hoster/MegasharesCom.py
deleted file mode 100644
index ed2363fe3..000000000
--- a/module/plugins/hoster/MegasharesCom.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MegasharesCom(SimpleHoster):
- __name__ = "MegasharesCom"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'http://(?:www\.)?(d\d{2}\.)?megashares\.com/((index\.php)?\?d\d{2}=|dl/)\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Megashares.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<h1 class="black xxl"[^>]*title="(?P<N>.+?)">'
- SIZE_PATTERN = r'<strong><span class="black">Filesize:</span></strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'<dd class="red">(Invalid Link Request|Link has been deleted|Invalid link)'
-
- LINK_PATTERN = r'<div id="show_download_button_%d".*?>\s*<a href="(.+?)">'
-
- PASSPORT_LEFT_PATTERN = r'Your Download Passport is: <.*?>(\w+).*?You have.*?<.*?>.*?([\d.]+) (\w+)'
- PASSPORT_RENEW_PATTERN = r'(\d+):<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, pyfile):
- self.handleDownload(True)
-
-
- def handleFree(self, pyfile):
- if self.NO_SLOTS_PATTERN in self.html:
- self.retry(wait_time=5 * 60)
-
- 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 _i in xrange(5):
- random_num = re.search(self.REACTIVATE_NUM_PATTERN, self.html).group(1)
-
- verifyinput = self.decryptCaptcha("http://d01.megashares.com/index.php",
- get={'secgfx': "gfx", 'random_num': random_num})
-
- self.logInfo(_("Reactivating passport %s: %s %s") % (passport_num, random_num, verifyinput))
-
- res = self.load("http://d01.megashares.com%s" % request_uri,
- get={'rs' : "check_passport_renewal",
- 'rsargs[]': verifyinput,
- 'rsargs[]': random_num,
- 'rsargs[]': passport_num,
- 'rsargs[]': "replace_sec_pprenewal",
- 'rsrnd[]' : str(int(time.time() * 1000))})
-
- if 'Thank you for reactivating your passport.' in res:
- self.correctCaptcha()
- self.retry()
- else:
- self.invalidCaptcha()
- else:
- self.fail(_("Failed to reactivate passport"))
-
- m = re.search(self.PASSPORT_RENEW_PATTERN, self.html)
- if m:
- time = [int(x) for x in m.groups()]
- renew = time[0] + (time[1] * 60) + (time[2] * 60)
- self.logDebug("Waiting %d seconds for a new passport" % renew)
- self.retry(wait_time=renew, reason=_("Passport renewal"))
-
- # Check traffic left on passport
- m = re.search(self.PASSPORT_LEFT_PATTERN, self.html, re.M | re.S)
- if m is None:
- self.fail(_("Passport not found"))
-
- self.logInfo(_("Download passport: %s") % m.group(1))
- data_left = float(m.group(2)) * 1024 ** {'B': 0, '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:
- self.retry(wait_time=600, reason=_("Passport renewal"))
-
- 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.error(msg)
-
- self.link = m.group(1)
- self.logDebug("%s: %s" % (msg, self.link))
-
-
-getInfo = create_getInfo(MegasharesCom)
diff --git a/module/plugins/hoster/MegauploadCom.py b/module/plugins/hoster/MegauploadCom.py
deleted file mode 100644
index 7fe305834..000000000
--- a/module/plugins/hoster/MegauploadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegauploadCom(DeadHoster):
- __name__ = "MegauploadCom"
- __type__ = "hoster"
- __version__ = "0.31"
-
- __pattern__ = r'http://(?:www\.)?megaupload\.com/\?.*&?(d|v)=\w+'
- __config__ = []
-
- __description__ = """Megaupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org")]
-
-
-getInfo = create_getInfo(MegauploadCom)
diff --git a/module/plugins/hoster/MegavideoCom.py b/module/plugins/hoster/MegavideoCom.py
deleted file mode 100644
index 0b4523e03..000000000
--- a/module/plugins/hoster/MegavideoCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class MegavideoCom(DeadHoster):
- __name__ = "MegavideoCom"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?megavideo\.com/\?.*&?(d|v)=\w+'
- __config__ = []
-
- __description__ = """Megavideo.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(MegavideoCom)
diff --git a/module/plugins/hoster/MovReelCom.py b/module/plugins/hoster/MovReelCom.py
deleted file mode 100644
index 2fe5184ae..000000000
--- a/module/plugins/hoster/MovReelCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class MovReelCom(XFSHoster):
- __name__ = "MovReelCom"
- __type__ = "hoster"
- __version__ = "1.24"
-
- __pattern__ = r'http://(?:www\.)?movreel\.com/\w{12}'
-
- __description__ = """MovReel.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("JorisV83", "jorisv83-pyload@yahoo.com")]
-
-
- LINK_PATTERN = r'<a href="(.+?)">Download Link'
-
-
-getInfo = create_getInfo(MovReelCom)
diff --git a/module/plugins/hoster/MultihostersCom.py b/module/plugins/hoster/MultihostersCom.py
deleted file mode 100644
index bcd7c6237..000000000
--- a/module/plugins/hoster/MultihostersCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.ZeveraCom import ZeveraCom
-
-
-class MultihostersCom(ZeveraCom):
- __name__ = "MultihostersCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)multihosters\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
-
- __description__ = """Multihosters.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("tjeh", "tjeh@gmx.net")]
diff --git a/module/plugins/hoster/MultishareCz.py b/module/plugins/hoster/MultishareCz.py
deleted file mode 100644
index bbb77f525..000000000
--- a/module/plugins/hoster/MultishareCz.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MultishareCz(SimpleHoster):
- __name__ = "MultishareCz"
- __type__ = "hoster"
- __version__ = "0.40"
-
- __pattern__ = r'http://(?:www\.)?multishare\.cz/stahnout/(?P<ID>\d+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """MultiShare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- SIZE_REPLACEMENTS = [('&nbsp;', '')]
-
- CHECK_TRAFFIC = True
- MULTI_HOSTER = True
-
- 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>'
-
-
- def handleFree(self, pyfile):
- self.download("http://www.multishare.cz/html/download_free.php", get={'ID': self.info['pattern']['ID']})
-
-
- def handlePremium(self, pyfile):
- self.download("http://www.multishare.cz/html/download_premium.php", get={'ID': self.info['pattern']['ID']})
-
-
- def handleMulti(self, pyfile):
- self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": pyfile.url}, decode=True)
-
- self.checkInfo()
-
- if not self.checkTrafficLeft():
- self.fail(_("Not enough credit left to download file"))
-
- self.download("http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random.random() * 10000 * random.random()),
- get={'u_ID' : self.acc_info['u_ID'],
- 'u_hash': self.acc_info['u_hash'],
- 'link' : pyfile.url},
- disposition=True)
-
-
-getInfo = create_getInfo(MultishareCz)
diff --git a/module/plugins/hoster/MyfastfileCom.py b/module/plugins/hoster/MyfastfileCom.py
deleted file mode 100644
index 662638843..000000000
--- a/module/plugins/hoster/MyfastfileCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class MyfastfileCom(MultiHoster):
- __name__ = "MyfastfileCom"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Myfastfile.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- def setup(self):
- self.chunkLimit = -1
-
-
- def handlePremium(self, pyfile):
- self.html = self.load('http://myfastfile.com/api.php',
- get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'],
- 'link': pyfile.url})
- self.logDebug("JSON data: " + self.html)
-
- self.html = json_loads(self.html)
- if self.html['status'] != 'ok':
- self.fail(_("Unable to unrestrict link"))
-
- self.link = self.html['link']
-
-
-getInfo = create_getInfo(MyfastfileCom)
diff --git a/module/plugins/hoster/MystoreTo.py b/module/plugins/hoster/MystoreTo.py
deleted file mode 100644
index 531368134..000000000
--- a/module/plugins/hoster/MystoreTo.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test link:
-# http://mystore.to/dl/mxcA50jKfP
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MystoreTo(SimpleHoster):
- __name__ = "MystoreTo"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?mystore\.to/dl/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Mystore.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "")]
-
-
- NAME_PATTERN = r'<h1>(?P<N>.+?)<'
- SIZE_PATTERN = r'FILESIZE: (?P<S>[\d\.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>file not found<'
-
-
- def setup(self):
- self.chunkLimit = 1
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- try:
- fid = re.search(r'wert="(.+?)"', self.html).group(1)
-
- except AttributeError:
- self.error(_("File-ID not found"))
-
- self.link = self.load("http://mystore.to/api/download",
- post={'FID': fid})
-
-
-getInfo = create_getInfo(MystoreTo)
diff --git a/module/plugins/hoster/MyvideoDe.py b/module/plugins/hoster/MyvideoDe.py
deleted file mode 100644
index 8af4a9a61..000000000
--- a/module/plugins/hoster/MyvideoDe.py
+++ /dev/null
@@ -1,49 +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.90"
-
- __pattern__ = r'http://(?:www\.)?myvideo\.de/watch/'
-
- __description__ = """Myvideo.de hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "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/NahrajCz.py b/module/plugins/hoster/NahrajCz.py
deleted file mode 100644
index 5f5003e22..000000000
--- a/module/plugins/hoster/NahrajCz.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class NahrajCz(DeadHoster):
- __name__ = "NahrajCz"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?nahraj\.cz/content/download/.+'
- __config__ = []
-
- __description__ = """Nahraj.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(NahrajCz)
diff --git a/module/plugins/hoster/NarodRu.py b/module/plugins/hoster/NarodRu.py
deleted file mode 100644
index b7380add0..000000000
--- a/module/plugins/hoster/NarodRu.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class NarodRu(SimpleHoster):
- __name__ = "NarodRu"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/\d+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Narod.ru hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<dt class="name">(?:<[^<]*>)*(?P<N>[^<]+)</dt>'
- SIZE_PATTERN = r'<dd class="size">(?P<S>\d[^<]*)</dd>'
- OFFLINE_PATTERN = r'<title>404</title>|Ѐайл уЎалеМ с сервОса|ЗакПМчОлся срПк храМеМОя файла\.'
-
- SIZE_REPLACEMENTS = [(u'КБ', 'KB'), (u'МБ', 'MB'), (u'ГБ', 'GB')]
- URL_REPLACEMENTS = [("narod.yandex.ru/", "narod.ru/"),
- (r"/start/\d+\.\w+-narod\.yandex\.ru/(\d{6,15})/\w+/(\w+)", r"/disk/\1/\2")]
-
- CAPTCHA_PATTERN = r'<number url="(.*?)">(\w+)</number>'
- LINK_FREE_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">'
-
-
- def handleFree(self, pyfile):
- for _i in xrange(5):
- self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(random.random() * 777))
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("Captcha"))
-
- post_data = {"action": "sendcapcha"}
- captcha_url, post_data['key'] = m.groups()
- post_data['rep'] = self.decryptCaptcha(captcha_url)
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = 'http://narod.ru' + m.group(1)
- self.correctCaptcha()
- break
-
- elif u'<b class="error-msg"><strong>ОшОблОсь?</strong>' in self.html:
- self.invalidCaptcha()
-
- else:
- self.error(_("Download link"))
-
- else:
- self.fail(_("No valid captcha code entered"))
-
-
-getInfo = create_getInfo(NarodRu)
diff --git a/module/plugins/hoster/NetloadIn.py b/module/plugins/hoster/NetloadIn.py
deleted file mode 100644
index cab7bd88f..000000000
--- a/module/plugins/hoster/NetloadIn.py
+++ /dev/null
@@ -1,297 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urlparse
-
-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):
- ## returns list of tupels (name, size (in bytes), status (see FileDatabase), url)
-
- apiurl = "http://api.netload.in/info.php"
- 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('ID') + ";"
-
- api = getURL(apiurl,
- get={'auth' : "Zf9SnQh9WiReEsb18akjvQGqT0I830e8",
- 'bz' : 1,
- 'md5' : 1,
- 'file_id': ids},
- decode=True)
-
- if api is None or len(api) < 10:
- self.logDebug("Prefetch failed")
- return
-
- if api.find("unknown_auth") >= 0:
- self.logDebug("Outdated auth code")
- return
-
- result = []
-
- for i, r in enumerate(api.splitlines()):
- try:
- tmp = r.split(";")
-
- try:
- size = int(tmp[2])
- except Exception:
- size = 0
-
- result.append((tmp[1], size, 2 if tmp[3] == "online" else 1, chunk[i] ))
-
- except Exception:
- self.logDebug("Error while processing response: %s" % r)
-
- yield result
-
-
-class NetloadIn(Hoster):
- __name__ = "NetloadIn"
- __type__ = "hoster"
- __version__ = "0.49"
-
- __pattern__ = r'https?://(?:www\.)?netload\.in/(?P<PATH>datei|index\.php\?id=10&file_id=)(?P<ID>\w+)'
-
- __description__ = """Netload.in hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("RaNaN", "ranan@pyload.org"),
- ("Gregy", "gregy@gregy.cz")]
-
-
- RECAPTCHA_KEY = "6LcLJMQSAAAAAJzquPUPKNovIhbK6LpSqCjYrsR1"
-
-
- 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.api_load()
-
- if self.api_data and self.api_data['filename']:
- self.pyfile.name = self.api_data['filename']
-
- if self.premium:
- self.logDebug("Use Premium Account")
-
- settings = self.load("http://www.netload.in/index.php", get={'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 api_load(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('ID')
- self.logDebug("URL: %s" % self.url)
- else:
- self.api_data = False
- return
-
- apiurl = "http://api.netload.in/info.php"
- html = self.load(apiurl, cookies=False,
- get={"file_id": match.group('ID'), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1",
- "md5": "1"}, decode=True).strip()
- if not html and n <= 3:
- self.setWait(2)
- self.wait()
- self.api_load(n + 1)
- return
-
- self.logDebug("APIDATA: " + html)
-
- self.api_data = {}
-
- if html and ";" in html and html not in ("unknown file_data", "unknown_server_data", "No input file specified."):
- lines = html.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.time(page)
-
- self.setWait(wait_time)
-
- self.logDebug("Final wait %d seconds" % wait_time)
-
- self.wait()
-
- self.url = self.get_file_url(page)
-
-
- def check_free_wait(self, page):
- if ">An access request has been made from IP address <" in page:
- self.wantReconnect = True
- self.setWait(self.get_wait_time.time(page) or 30)
- self.wait()
- return True
- else:
- return False
-
-
- def download_html(self):
- page = self.load(self.url, decode=True)
-
- 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.M)
- # 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(5):
- if not page:
- page = self.load(self.url)
- t = time.time() + 30
-
- if "/share/templates/download_hddcrash.tpl" in page:
- self.logError(_("Netload HDD Crash"))
- self.fail(_("File temporarily not available"))
-
- self.logDebug("Try number %d " % i)
-
- if ">Your download is being prepared.<" in page:
- self.logDebug("We will prepare your download")
- self.final_wait(page)
- return True
-
- self.logDebug("Trying to find captcha")
-
- try:
- url_captcha_html = re.search(r'(index.php\?id=10&amp;.*&amp;captcha=1)', page).group(1).replace("amp;", "")
-
- except Exception, e:
- self.logDebug("Exception during Captcha regex: %s" % e.message)
- page = None
-
- else:
- url_captcha_html = urlparse.urljoin("http://netload.in/", url_captcha_html)
- break
-
- self.html = self.load(url_captcha_html)
-
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- response_page = self.load("http://www.netload.in/index.php?id=10",
- post={'captcha_check' : '1',
- 'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response,
- 'file_id' : self.api_data['fileid'],
- 'Download_Next' : ''})
- if "Orange_Link" in response_page:
- break
-
- if self.check_free_wait(response_page):
- self.logDebug("Had to wait for next free slot, trying again")
- return self.download_html()
-
- else:
- download_url = self.get_file_url(response_page)
- self.logDebug("Download URL after get_file: " + download_url)
- if not download_url.startswith("http://"):
- self.error(_("Download url: %s") % download_url)
- self.wait()
-
- self.url = download_url
- return True
-
-
- 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:
- return attempt.group(1)
- else:
- self.logDebug("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 Exception, e:
- self.logDebug("Getting final link failed", e.message)
- return None
-
-
- def get_wait_time.time(self, page):
- return int(re.search(r"countdown\((.+),'change\(\)'\)", page).group(1)) / 100
-
-
- def proceed(self, url):
- 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/NitroflareCom.py b/module/plugins/hoster/NitroflareCom.py
deleted file mode 100644
index 40bf0749f..000000000
--- a/module/plugins/hoster/NitroflareCom.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster
-
-
-class NitroflareCom(SimpleHoster):
- __name__ = "NitroflareCom"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'https?://(?:www\.)?nitroflare\.com/view/(?P<ID>[\w^_]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Nitroflare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sahil", "sahilshekhawat01@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com"),
- ("Stickell", "l.stickell@yahoo.it")]
-
- # URL_REPLACEMENTS = [("http://", "https://")]
-
- INFO_PATTERN = r'title="(?P<N>.+?)".+>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'>File doesn\'t exist'
-
- LINK_FREE_PATTERN = r'(https?://[\w\-]+\.nitroflare\.com/.+?)"'
-
- RECAPTCHA_KEY = "6Lenx_USAAAAAF5L1pmTWvWcH73dipAEzNnmNLgy"
-
- PREMIUM_ONLY_PATTERN = r'This file is available with Premium only'
- WAIT_PATTERN = r'You have to wait .+?<'
- ERROR_PATTERN = r'downloading is not possible'
-
-
- def checkErrors(self):
- if not self.html:
- return
-
- if not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
- self.fail(_("Link require a premium account to be handled"))
-
- elif hasattr(self, 'WAIT_PATTERN'):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1}[u.lower()] for v, u in
- re.findall(r'(\d+)\s*(hr|hour|min|sec)', m.group(0), re.I))
- self.wait(wait_time, wait_time > 300)
- return
-
- elif hasattr(self, 'ERROR_PATTERN'):
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- errmsg = self.info['error'] = m.group(1)
- self.error(errmsg)
-
- self.info.pop('error', None)
-
-
- def handleFree(self, pyfile):
- # used here to load the cookies which will be required later
- self.load(pyfile.url, post={'goToFreePage': ""})
-
- self.load("http://nitroflare.com/ajax/setCookie.php", post={'fileId': self.info['pattern']['ID']})
- self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
- post={'method': "startTimer", 'fileId': self.info['pattern']['ID']})
-
- self.checkErrors()
-
- try:
- js_file = self.load("http://nitroflare.com/js/downloadFree.js?v=1.0.1")
- var_time = re.search("var time = (\\d+);", js_file)
- wait_time = int(var_time.groups()[0])
-
- except Exception:
- wait_time = 60
-
- self.wait(wait_time)
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
- post={'method' : "fetchDownload",
- 'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- if "The captcha wasn't entered correctly" in self.html:
- self.logWarning("The captcha wasn't entered correctly")
- return
-
- if "You have to fill the captcha" in self.html:
- self.logWarning("Captcha unfilled")
- return
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
- else:
- self.logError("Unable to detect direct link")
diff --git a/module/plugins/hoster/NoPremiumPl.py b/module/plugins/hoster/NoPremiumPl.py
deleted file mode 100644
index c3313c525..000000000
--- a/module/plugins/hoster/NoPremiumPl.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class NoPremiumPl(MultiHoster):
- __name__ = "NoPremiumPl"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://direct\.nopremium\.pl.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """NoPremium.pl multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@nopremium.pl")]
-
-
- API_URL = "http://crypt.nopremium.pl"
-
- API_QUERY = {'site' : "nopremium",
- 'output' : "json",
- 'username': "",
- 'password': "",
- 'url' : ""}
-
- ERROR_CODES = {0 : "[%s] Incorrect login credentials",
- 1 : "[%s] Not enough transfer to download - top-up your account",
- 2 : "[%s] Incorrect / dead link",
- 3 : "[%s] Error connecting to hosting, try again later",
- 9 : "[%s] Premium account has expired",
- 15: "[%s] Hosting no longer supported",
- 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
-
-
- def prepare(self):
- super(NoPremiumPl, self).prepare()
-
- data = self.account.getAccountData(self.user)
-
- self.usr = data['usr']
- self.pwd = data['pwd']
-
-
- def runFileQuery(self, url, mode=None):
- query = self.API_QUERY.copy()
-
- query["username"] = self.usr
- query["password"] = self.pwd
- query["url"] = url
-
- if mode == "fileinfo":
- query['check'] = 2
- query['loc'] = 1
-
- self.logDebug(query)
-
- return self.load(self.API_URL, post=query)
-
-
- def handleFree(self, pyfile):
- try:
- data = self.runFileQuery(pyfile.url, 'fileinfo')
-
- except Exception:
- self.logDebug("runFileQuery error")
- self.tempOffline()
-
- try:
- parsed = json_loads(data)
-
- except Exception:
- self.logDebug("loads error")
- self.tempOffline()
-
- self.logDebug(parsed)
-
- if "errno" in parsed.keys():
- if parsed["errno"] in self.ERROR_CODES:
- # error code in known
- self.fail(self.ERROR_CODES[parsed["errno"]] % self.__name__)
- else:
- # error code isn't yet added to plugin
- self.fail(
- parsed["errstring"]
- or _("Unknown error (code: %s)") % parsed["errno"]
- )
-
- if "sdownload" in parsed:
- if parsed["sdownload"] == "1":
- self.fail(
- _("Download from %s is possible only using NoPremium.pl website \
- directly") % parsed["hosting"])
-
- pyfile.name = parsed["filename"]
- pyfile.size = parsed["filesize"]
-
- try:
- self.link = self.runFileQuery(pyfile.url, 'filedownload')
-
- except Exception:
- self.logDebug("runFileQuery error #2")
- self.tempOffline()
diff --git a/module/plugins/hoster/NosuploadCom.py b/module/plugins/hoster/NosuploadCom.py
deleted file mode 100644
index 241ee3a29..000000000
--- a/module/plugins/hoster/NosuploadCom.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class NosuploadCom(XFSHoster):
- __name__ = "NosuploadCom"
- __type__ = "hoster"
- __version__ = "0.31"
-
- __pattern__ = r'http://(?:www\.)?nosupload\.com/\?d=\w{12}'
-
- __description__ = """Nosupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- SIZE_PATTERN = r'<p><strong>Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)</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, 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.M | re.S).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, 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 b00f71635..000000000
--- a/module/plugins/hoster/NovafileCom.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://novafile.com/vfun4z6o2cit
-# http://novafile.com/s6zrr5wemuz4
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class NovafileCom(XFSHoster):
- __name__ = "NovafileCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?novafile\.com/\w{12}'
-
- __description__ = """Novafile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- ERROR_PATTERN = r'class="alert.+?alert-separate".*?>\s*(?:<p>)?(.*?)\s*</'
- WAIT_PATTERN = r'<p>Please wait <span id="count".*?>(\d+)</span> seconds</p>'
-
- LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>'
-
-
-getInfo = create_getInfo(NovafileCom)
diff --git a/module/plugins/hoster/NowDownloadSx.py b/module/plugins/hoster/NowDownloadSx.py
deleted file mode 100644
index a1cf9baf7..000000000
--- a/module/plugins/hoster/NowDownloadSx.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.utils import fixup
-
-
-class NowDownloadSx(SimpleHoster):
- __name__ = "NowDownloadSx"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'http://(?:www\.)?(nowdownload\.[a-zA-Z]{2,}/(dl/|download\.php.+?id=|mobile/(#/files/|.+?id=))|likeupload\.org/)\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """NowDownload.sx hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("godofdream", "soilfiction@gmail.com"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- INFO_PATTERN = r'Downloading</span> <br> (?P<N>.*) (?P<S>[\d.,]+) (?P<U>[\w^_]+) </h4>'
- OFFLINE_PATTERN = r'>This file does not exist'
-
- TOKEN_PATTERN = r'"(/api/token\.php\?token=\w+)"'
- CONTINUE_PATTERN = r'"(/dl2/\w+/\w+)"'
- WAIT_PATTERN = r'\.countdown\(\{until: \+(\d+),'
- LINK_FREE_PATTERN = r'(http://s\d+\.coolcdn\.info/nowdownload/.+?)["\']'
-
- NAME_REPLACEMENTS = [("&#?\w+;", fixup), (r'<.*?>', '')]
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = -1
-
-
- def handleFree(self, pyfile):
- 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.error()
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait = int(m.group(1))
- else:
- wait = 60
-
- baseurl = "http://www.nowdownload.at"
- self.html = self.load(baseurl + str(tokenlink.group(1)))
- self.wait(wait)
-
- self.html = self.load(baseurl + str(continuelink.group(1)))
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Download link not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(NowDownloadSx)
diff --git a/module/plugins/hoster/NowVideoSx.py b/module/plugins/hoster/NowVideoSx.py
deleted file mode 100644
index 477379597..000000000
--- a/module/plugins/hoster/NowVideoSx.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class NowVideoSx(SimpleHoster):
- __name__ = "NowVideoSx"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?nowvideo\.[a-zA-Z]{2,}/(video/|mobile/(#/videos/|.+?id=))(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """NowVideo.sx hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.nowvideo.sx/video/\g<ID>')]
-
- NAME_PATTERN = r'<h4>(?P<N>.+?)<'
- OFFLINE_PATTERN = r'>This file no longer exists'
-
- LINK_FREE_PATTERN = r'<source src="(.+?)"'
- LINK_PREMIUM_PATTERN = r'<div id="content_player" >\s*<a href="(.+?)"'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- self.html = self.load("http://www.nowvideo.sx/mobile/video.php", get={'id': self.info['pattern']['ID']})
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(NowVideoSx)
diff --git a/module/plugins/hoster/OboomCom.py b/module/plugins/hoster/OboomCom.py
deleted file mode 100644
index 725763345..000000000
--- a/module/plugins/hoster/OboomCom.py
+++ /dev/null
@@ -1,145 +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.31"
-
- __pattern__ = r'https?://(?:www\.)?oboom\.com/(#(id=|/)?)?(?P<ID>\w{8})'
-
- __description__ = """oboom.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stanley", "stanley.foerster@gmail.com")]
-
-
- RECAPTCHA_KEY = "6LdqpO0SAAAAAJGHXo63HyalP7H4qlRs_vff0kJX"
-
-
- def setup(self):
- self.chunkLimit = 1
- self.multiDL = self.resumeDownload = 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})
-
-
- 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 _i in xrange(5):
- response, challenge = 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], 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/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]
- elif result[0] == 421:
- self.retry(wait_time=result[2] + 60, reason=_("Connection limit exceeded"))
- else:
- self.fail(_("Could not retrieve download ticket. Error code: %s") % result[0])
diff --git a/module/plugins/hoster/OneFichierCom.py b/module/plugins/hoster/OneFichierCom.py
deleted file mode 100644
index 4b947c554..000000000
--- a/module/plugins/hoster/OneFichierCom.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class OneFichierCom(SimpleHoster):
- __name__ = "OneFichierCom"
- __type__ = "hoster"
- __version__ = "0.84"
-
- __pattern__ = r'https?://(?:www\.)?(?:(?P<ID1>\w+)\.)?(?P<HOST>1fichier\.com|alterupload\.com|cjoint\.net|d(es)?fichiers\.com|dl4free\.com|megadl\.fr|mesfichiers\.org|piecejointe\.net|pjointe\.com|tenvoi\.com)(?:/\?(?P<ID2>\w+))?'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """1fichier.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("the-razer", "daniel_ AT gmx DOT net"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("imclem", None),
- ("stickell", "l.stickell@yahoo.it"),
- ("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
- ("Walter Purcaro", "vuolter@gmail.com"),
- ("Ludovic Lehmann", "ludo.lehmann@gmail.com")]
-
-
- COOKIES = [("1fichier.com", "LG", "en")]
- DISPOSITION = False #: Remove in 0.4.10
-
- NAME_PATTERN = r'>FileName :</td>\s*<td.*>(?P<N>.+?)<'
- SIZE_PATTERN = r'>Size :</td>\s*<td.*>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'File not found !\s*<'
-
- WAIT_PATTERN = r'>You must wait \d+ minutes'
-
-
- def setup(self):
- self.multiDL = self.premium
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- id = self.info['pattern']['ID1'] or self.info['pattern']['ID2']
- url, inputs = self.parseHtmlForm('action="https://1fichier.com/\?%s' % id)
-
- if not url:
- self.fail(_("Download link not found"))
-
- if "pass" in inputs:
- inputs['pass'] = self.getPassword()
-
- inputs['submit'] = "Download"
-
- self.download(url, post=inputs)
-
-
- def handlePremium(self, pyfile):
- self.download(pyfile.url, post={'dl': "Download", 'did': 0})
-
-
-getInfo = create_getInfo(OneFichierCom)
diff --git a/module/plugins/hoster/OpenloadIo.py b/module/plugins/hoster/OpenloadIo.py
deleted file mode 100644
index c31b5b997..000000000
--- a/module/plugins/hoster/OpenloadIo.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class OpenloadIo(SimpleHoster):
- __name__ = "OpenloadIo"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?openload\.io/f/\w{11}'
-
- __description__ = """Openload.io hoster plugin"""
- __license__ = "GPLv3"
-
- NAME_PATTERN = r'<span id="filename">(?P<N>.+)</'
- SIZE_PATTERN = r'<span class="count">(?P<S>[\d.,]+) (?P<U>[\w^_]+)<'
- OFFLINE_PATTERN = r">(We can't find the file you are looking for)"
-
- LINK_FREE_PATTERN = r'id="realdownload"><a href="(https?://[\w\.]+\.openload\.io/dl/.*?)"'
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-getInfo = create_getInfo(OpenloadIo)
diff --git a/module/plugins/hoster/OronCom.py b/module/plugins/hoster/OronCom.py
deleted file mode 100644
index 7002d26ab..000000000
--- a/module/plugins/hoster/OronCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class OronCom(DeadHoster):
- __name__ = "OronCom"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'https?://(?:www\.)?oron\.com/\w{12}'
- __config__ = []
-
- __description__ = """Oron.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("chrox", "chrox@pyload.org"),
- ("DHMH", "DHMH@pyload.org")]
-
-
-getInfo = create_getInfo(OronCom)
diff --git a/module/plugins/hoster/OverLoadMe.py b/module/plugins/hoster/OverLoadMe.py
deleted file mode 100644
index d06baa0f5..000000000
--- a/module/plugins/hoster/OverLoadMe.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class OverLoadMe(MultiHoster):
- __name__ = "OverLoadMe"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'https?://.*overload\.me/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Over-Load.me multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("marley", "marley@over-load.me")]
-
-
- def setup(self):
- self.chunkLimit = 5
-
-
- def handlePremium(self, pyfile):
- https = "https" if self.getConfig('ssl') else "http"
- 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(data)
-
- if data['error'] == 1:
- self.logWarning(data['msg'])
- self.tempOffline()
- else:
- if pyfile.name and pyfile.name.endswith('.tmp') and data['filename']:
- pyfile.name = data['filename']
- pyfile.size = parseFileSize(data['filesize'])
-
- http_repl = ["http://", "https://"]
- self.link = data['downloadlink'].replace(*http_repl if self.getConfig('ssl') else *http_repl[::-1])
-
-
-getInfo = create_getInfo(OverLoadMe)
diff --git a/module/plugins/hoster/PandaplaNet.py b/module/plugins/hoster/PandaplaNet.py
deleted file mode 100644
index a5716e4d4..000000000
--- a/module/plugins/hoster/PandaplaNet.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class PandaplaNet(DeadHoster):
- __name__ = "PandaplaNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?pandapla\.net/\w{12}'
- __config__ = []
-
- __description__ = """Pandapla.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
-getInfo = create_getInfo(PandaplaNet)
diff --git a/module/plugins/hoster/PornhostCom.py b/module/plugins/hoster/PornhostCom.py
deleted file mode 100644
index 0c3b84a9d..000000000
--- a/module/plugins/hoster/PornhostCom.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class PornhostCom(Hoster):
- __name__ = "PornhostCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?pornhost\.com/(\d+/\d+\.html|\d+)'
-
- __description__ = """Pornhost.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "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\d+\.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\d+\.pornhost\.com/\d+/.*?"',
- 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\d+\.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|You will be redirected to', self.html):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/PornhubCom.py b/module/plugins/hoster/PornhubCom.py
deleted file mode 100644
index 16ce36ea9..000000000
--- a/module/plugins/hoster/PornhubCom.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class PornhubCom(Hoster):
- __name__ = "PornhubCom"
- __type__ = "hoster"
- __version__ = "0.50"
-
- __pattern__ = r'http://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=\w+'
-
- __description__ = """Pornhub.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "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.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):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/PotloadCom.py b/module/plugins/hoster/PotloadCom.py
deleted file mode 100644
index 0eed158a4..000000000
--- a/module/plugins/hoster/PotloadCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class PotloadCom(DeadHoster):
- __name__ = "PotloadCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?potload\.com/\w{12}'
- __config__ = []
-
- __description__ = """Potload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(PotloadCom)
diff --git a/module/plugins/hoster/PremiumTo.py b/module/plugins/hoster/PremiumTo.py
deleted file mode 100644
index ab5016673..000000000
--- a/module/plugins/hoster/PremiumTo.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import fs_encode
-
-
-class PremiumTo(MultiHoster):
- __name__ = "PremiumTo"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Premium.to multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("stickell", "l.stickell@yahoo.it")]
-
-
- CHECK_TRAFFIC = True
-
-
- def handlePremium(self, pyfile):
- #raise timeout to 2min
- self.download("http://premium.to/api/getfile.php",
- get={'username': self.account.username,
- 'password': self.account.password,
- 'link' : pyfile.url},
- disposition=True)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'nopremium': "No premium account available"}):
- self.retry(60, 5 * 60, "No premium account available")
-
- err = ''
- if self.req.http.code == '420':
- # Custom error code send - fail
- file = fs_encode(self.lastDownload)
- with open(file, "rb") as f:
- err = f.read(256).strip()
- os.remove(file)
-
- if err:
- self.fail(err)
-
- return super(PremiumTo, self).checkFile(rules)
-
-
-getInfo = create_getInfo(PremiumTo)
diff --git a/module/plugins/hoster/PremiumizeMe.py b/module/plugins/hoster/PremiumizeMe.py
deleted file mode 100644
index 3227fb001..000000000
--- a/module/plugins/hoster/PremiumizeMe.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class PremiumizeMe(MultiHoster):
- __name__ = "PremiumizeMe"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.coreReady
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Premiumize.me multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Florian Franzen", "FlorianFranzen@gmail.com")]
-
-
- def handlePremium(self, pyfile):
- # 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)
- data = json_loads(self.load("https://api.premiumize.me/pm-api/v1.php",
- get={'method' : "directdownloadlink",
- 'params[login]': user,
- 'params[pass]' : data['password'],
- 'params[link]' : pyfile.url}))
-
- # Check status and decide what to do
- status = data['status']
-
- if status == 200:
- self.link = data['result']['location']
- return
-
- elif status == 400:
- self.fail(_("Invalid link"))
-
- elif status == 404:
- self.offline()
-
- elif status >= 500:
- self.tempOffline()
-
- else:
- self.fail(data['statusmessage'])
-
-
-getInfo = create_getInfo(PremiumizeMe)
diff --git a/module/plugins/hoster/PromptfileCom.py b/module/plugins/hoster/PromptfileCom.py
deleted file mode 100644
index 3815a1a24..000000000
--- a/module/plugins/hoster/PromptfileCom.py
+++ /dev/null
@@ -1,46 +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.13"
-
- __pattern__ = r'https?://(?:www\.)?promptfile\.com/'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Promptfile.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- 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_FREE_PATTERN = r'<a href=\"(.+)\" target=\"_blank\" class=\"view_dl_link\">Download File</a>'
-
-
- def handleFree(self, pyfile):
- # STAGE 1: get link to continue
- m = re.search(self.CHASH_PATTERN, self.html)
- if m is None:
- self.error(_("CHASH_PATTERN not found"))
- chash = m.group(1)
- self.logDebug("Read chash %s" % chash)
- # continue to stage2
- self.html = self.load(pyfile.url, decode=True, post={'chash': chash})
-
- # STAGE 2: get the direct link
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(PromptfileCom)
diff --git a/module/plugins/hoster/PrzeklejPl.py b/module/plugins/hoster/PrzeklejPl.py
deleted file mode 100644
index 68fd6ca6d..000000000
--- a/module/plugins/hoster/PrzeklejPl.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class PrzeklejPl(DeadHoster):
- __name__ = "PrzeklejPl"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?przeklej\.pl/plik/.+'
- __config__ = []
-
- __description__ = """Przeklej.pl hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(PrzeklejPl)
diff --git a/module/plugins/hoster/PutdriveCom.py b/module/plugins/hoster/PutdriveCom.py
deleted file mode 100644
index 7f4b7b6cc..000000000
--- a/module/plugins/hoster/PutdriveCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.ZeveraCom import ZeveraCom
-
-
-class PutdriveCom(ZeveraCom):
- __name__ = "PutdriveCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)putdrive\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
-
- __description__ = """Multihosters.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/module/plugins/hoster/QuickshareCz.py b/module/plugins/hoster/QuickshareCz.py
deleted file mode 100644
index 1e0750b88..000000000
--- a/module/plugins/hoster/QuickshareCz.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class QuickshareCz(SimpleHoster):
- __name__ = "QuickshareCz"
- __type__ = "hoster"
- __version__ = "0.56"
-
- __pattern__ = r'http://(?:[^/]*\.)?quickshare\.cz/stahnout-soubor/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Quickshare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<th width="145px">Název:</th>\s*<td style="word-wrap:break-word;">(?P<N>[^<]+)</td>'
- SIZE_PATTERN = r'<th>Velikost:</th>\s*<td>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</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+) = ([\d.]+|'.+?')", 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(pyfile)
- else:
- self.handleFree(pyfile)
-
- if self.checkDownload({"error": re.compile(r"\AChyba!")}, max_size=100):
- self.fail(_("File not m or plugin defect"))
-
-
- def handleFree(self, pyfile):
- # 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(pycurl.FOLLOWLOCATION, 0)
- self.load(download_url, post=data)
- self.header = self.req.http.header
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
- m = re.search(r'Location\s*:\s*(.+)', self.header, re.I)
- if m is None:
- self.fail(_("File not found"))
-
- self.link = m.group(1).rstrip() #@TODO: Remove .rstrip() in 0.4.10
- self.logDebug("FREE URL2:" + self.link)
-
- # check errors
- m = re.search(r'/chyba/(\d+)', self.link)
- 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))
-
-
- def handlePremium(self, pyfile):
- 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.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 710faf25c..000000000
--- a/module/plugins/hoster/RPNetBiz.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.common.json_layer import json_loads
-
-
-class RPNetBiz(MultiHoster):
- __name__ = "RPNetBiz"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'https?://.+rpnet\.biz'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """RPNet.biz multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Dman", "dmanugm@gmail.com")]
-
-
- def setup(self):
- self.chunkLimit = -1
-
-
- def handlePremium(self, pyfile):
- user, data = self.account.selectAccount()
-
- # Get the download link
- res = 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" % res)
- link_status = json_loads(res)['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))
- res = 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" % res)
- download_status = json_loads(res)['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.link = link_status['generated']
- return
- elif 'error' in link_status:
- self.fail(link_status['error'])
- else:
- self.fail(_("Something went wrong, not supposed to enter here"))
-
-
-getInfo = create_getInfo(RPNetBiz)
diff --git a/module/plugins/hoster/RapideoPl.py b/module/plugins/hoster/RapideoPl.py
deleted file mode 100644
index 50804e8cd..000000000
--- a/module/plugins/hoster/RapideoPl.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class RapideoPl(MultiHoster):
- __name__ = "RapideoPl"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Rapideo.pl multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("goddie", "dev@rapideo.pl")]
-
-
- API_URL = "http://enc.rapideo.pl"
-
- API_QUERY = {'site' : "newrd",
- 'output' : "json",
- 'username': "",
- 'password': "",
- 'url' : ""}
-
- ERROR_CODES = {0 : "[%s] Incorrect login credentials",
- 1 : "[%s] Not enough transfer to download - top-up your account",
- 2 : "[%s] Incorrect / dead link",
- 3 : "[%s] Error connecting to hosting, try again later",
- 9 : "[%s] Premium account has expired",
- 15: "[%s] Hosting no longer supported",
- 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
-
-
- def prepare(self):
- super(RapideoPl, self).prepare()
-
- data = self.account.getAccountData(self.user)
-
- self.usr = data['usr']
- self.pwd = data['pwd']
-
-
- def runFileQuery(self, url, mode=None):
- query = self.API_QUERY.copy()
-
- query["username"] = self.usr
- query["password"] = self.pwd
- query["url"] = url
-
- if mode == "fileinfo":
- query['check'] = 2
- query['loc'] = 1
-
- self.logDebug(query)
-
- return self.load(self.API_URL, post=query)
-
-
- def handleFree(self, pyfile):
- try:
- data = self.runFileQuery(pyfile.url, 'fileinfo')
-
- except Exception:
- self.logDebug("RunFileQuery error")
- self.tempOffline()
-
- try:
- parsed = json_loads(data)
-
- except Exception:
- self.logDebug("Loads error")
- self.tempOffline()
-
- self.logDebug(parsed)
-
- if "errno" in parsed.keys():
- if parsed["errno"] in self.ERROR_CODES:
- # error code in known
- self.fail(self.ERROR_CODES[parsed["errno"]] % self.__name__)
- else:
- # error code isn't yet added to plugin
- self.fail(
- parsed["errstring"]
- or _("Unknown error (code: %s)") % parsed["errno"]
- )
-
- if "sdownload" in parsed:
- if parsed["sdownload"] == "1":
- self.fail(
- _("Download from %s is possible only using Rapideo.pl website \
- directly") % parsed["hosting"])
-
- pyfile.name = parsed["filename"]
- pyfile.size = parsed["filesize"]
-
- try:
- self.link = self.runFileQuery(pyfile.url, 'filedownload')
-
- except Exception:
- self.logDebug("runFileQuery error #2")
- self.tempOffline()
diff --git a/module/plugins/hoster/RapidfileshareNet.py b/module/plugins/hoster/RapidfileshareNet.py
deleted file mode 100644
index 0bbaed57f..000000000
--- a/module/plugins/hoster/RapidfileshareNet.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class RapidfileshareNet(XFSHoster):
- __name__ = "RapidfileshareNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?rapidfileshare\.net/\w{12}'
-
- __description__ = """Rapidfileshare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("guidobelix", "guidobelix@hotmail.it")]
-
-
- NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>.+?)">'
- SIZE_PATTERN = r'>http://www.rapidfileshare.net/\w+?</font> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</font>'
-
- OFFLINE_PATTERN = r'>No such file with this filename'
- TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
-
-
-getInfo = create_getInfo(RapidfileshareNet)
diff --git a/module/plugins/hoster/RapidgatorNet.py b/module/plugins/hoster/RapidgatorNet.py
deleted file mode 100644
index ae74e8a63..000000000
--- a/module/plugins/hoster/RapidgatorNet.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.common.json_layer import json_loads
-from module.network.HTTPRequest import BadHeader
-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.33"
-
- __pattern__ = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Rapidgator.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("chrox", None),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- API_URL = "http://rapidgator.net/api/file"
-
- COOKIES = [("rapidgator.net", "lang", "en")]
-
- NAME_PATTERN = r'<title>Download file (?P<N>.*)</title>'
- 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_PATTERN = r'You can download files up to|This file can be downloaded by premium only<'
- 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).+'
-
- LINK_FREE_PATTERN = r'return \'(http://\w+.rapidgator.net/.*)\';'
-
- RECAPTCHA_PATTERN = r'"http://api\.recaptcha\.net/challenge\?k=(.*?)"'
- ADSCAPTCHA_PATTERN = r'(http://api\.adscaptcha\.com/Get\.aspx[^"\']+)'
- SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.script\?k=(.*?)"'
-
-
- def setup(self):
- if self.account:
- self.sid = self.account.getAccountInfo(self.user).get('sid', None)
- else:
- self.sid = None
-
- if self.sid:
- self.premium = True
-
- self.resumeDownload = self.multiDL = self.premium
- self.chunkLimit = 1
-
-
- 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, pyfile):
- self.api_data = self.api_response('info')
- self.api_data['md5'] = self.api_data['hash']
-
- pyfile.name = self.api_data['filename']
- pyfile.size = self.api_data['size']
-
- self.link = self.api_response('download')['url']
-
-
- def handleFree(self, pyfile):
- jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html))
- self.logDebug(jsvars)
-
- self.req.http.lastURL = pyfile.url
- self.req.http.c.setopt(pycurl.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(jsvars.get('secs', 45), False)
-
- url = "http://rapidgator.net%s?sid=%s" % (
- jsvars.get('getDownloadUrl', '/download/AjaxGetDownload'), jsvars['sid'])
- jsvars.update(self.getJsonResponse(url))
-
- self.req.http.lastURL = pyfile.url
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
-
- url = "http://rapidgator.net%s" % jsvars.get('captchaUrl', '/download/captcha')
- self.html = self.load(url)
-
- for _i in xrange(5):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
- break
- else:
- captcha = self.handleCaptcha()
-
- if not captcha:
- self.error(_("Captcha pattern not found"))
-
- response, challenge = captcha.challenge()
-
- self.html = self.load(url, post={'DownloadCaptchaForm[captcha]': "",
- 'adcopy_challenge' : challenge,
- 'adcopy_response' : response})
-
- if "The verification code is incorrect" in self.html:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- else:
- self.error(_("Download link"))
-
-
- def handleCaptcha(self):
- for klass in (AdsCaptcha, ReCaptcha, SolveMedia):
- inst = klass(self)
- if inst.detect_key():
- return inst
-
-
- def getJsonResponse(self, url):
- res = self.load(url, decode=True)
- if not res.startswith('{'):
- self.retry()
- self.logDebug(url, res)
- return json_loads(res)
-
-
-getInfo = create_getInfo(RapidgatorNet)
diff --git a/module/plugins/hoster/RapiduNet.py b/module/plugins/hoster/RapiduNet.py
deleted file mode 100644
index fcccbbebc..000000000
--- a/module/plugins/hoster/RapiduNet.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-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 RapiduNet(SimpleHoster):
- __name__ = "RapiduNet"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'https?://(?:www\.)?rapidu\.net/(?P<ID>\d{10})'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Rapidu.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("prOq", "")]
-
-
- COOKIES = [("rapidu.net", "rapidu_lang", "en")]
-
- INFO_PATTERN = r'<h1 title="(?P<N>.*)">.*</h1>\s*<small>(?P<S>\d+(\.\d+)?)\s(?P<U>\w+)</small>'
- OFFLINE_PATTERN = r'<h1>404'
-
- ERROR_PATTERN = r'<div class="error">'
-
- RECAPTCHA_KEY = r'6Ld12ewSAAAAAHoE6WVP_pSfCdJcBQScVweQh8Io'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = self.premium
-
-
- def handleFree(self, pyfile):
- self.req.http.lastURL = pyfile.url
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
-
- jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
- get={'a': "getLoadTimeToDownload"},
- post={'_go': ""},
- decode=True)
-
- if str(jsvars['timeToDownload']) is "stop":
- t = (24 * 60 * 60) - (int(time.time()) % (24 * 60 * 60)) + time.altzone
-
- self.logInfo("You've reach your daily download transfer")
-
- self.retry(10, 10 if t < 1 else None, _("Try tomorrow again")) #@NOTE: check t in case of not synchronised clock
-
- else:
- self.wait(int(jsvars['timeToDownload']) - int(time.time()))
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
- get={'a': "getCheckCaptcha"},
- post={'_go' : "",
- 'captcha1': challenge,
- 'captcha2': response,
- 'fileId' : self.info['pattern']['ID']},
- decode=True)
-
- if jsvars['message'] == 'success':
- self.link = jsvars['url']
-
-
- def getJsonResponse(self, *args, **kwargs):
- res = self.load(*args, **kwargs)
- if not res.startswith('{'):
- self.retry()
-
- self.logDebug(res)
-
- return json_loads(res)
-
-
-getInfo = create_getInfo(RapiduNet)
diff --git a/module/plugins/hoster/RarefileNet.py b/module/plugins/hoster/RarefileNet.py
deleted file mode 100644
index a45e4ed4d..000000000
--- a/module/plugins/hoster/RarefileNet.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class RarefileNet(XFSHoster):
- __name__ = "RarefileNet"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'http://(?:www\.)?rarefile\.net/\w{12}'
-
- __description__ = """Rarefile.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- LINK_PATTERN = r'<a href="(.+?)">\1</a>'
-
-
-getInfo = create_getInfo(RarefileNet)
diff --git a/module/plugins/hoster/RealdebridCom.py b/module/plugins/hoster/RealdebridCom.py
deleted file mode 100644
index 4500fcefc..000000000
--- a/module/plugins/hoster/RealdebridCom.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urllib
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class RealdebridCom(MultiHoster):
- __name__ = "RealdebridCom"
- __type__ = "hoster"
- __version__ = "0.67"
-
- __pattern__ = r'https?://((?:www\.|s\d+\.)?real-debrid\.com/dl/|[\w^_]\.rdb\.so/d/)[\w^_]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Real-Debrid.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
-
-
- def setup(self):
- self.chunkLimit = 3
-
-
- def handlePremium(self, pyfile):
- data = json_loads(self.load("https://real-debrid.com/ajax/unrestrict.php",
- get={'lang' : "en",
- 'link' : pyfile.url,
- 'password': self.getPassword(),
- 'time' : int(time.time() * 1000)}))
-
- 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 and pyfile.name.endswith('.tmp') and data['file_name']:
- pyfile.name = data['file_name']
- pyfile.size = parseFileSize(data['file_size'])
- self.link = data['generated_links'][0][-1]
-
- if self.getConfig('ssl'):
- self.link = self.link.replace("http://", "https://")
- else:
- self.link = self.link.replace("https://", "http://")
-
-
-getInfo = create_getInfo(RealdebridCom)
diff --git a/module/plugins/hoster/RedtubeCom.py b/module/plugins/hoster/RedtubeCom.py
deleted file mode 100644
index 9051c498a..000000000
--- a/module/plugins/hoster/RedtubeCom.py
+++ /dev/null
@@ -1,62 +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.20"
-
- __pattern__ = r'http://(?:www\.)?redtube\.com/\d+'
-
- __description__ = """Redtube.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "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):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/RehostTo.py b/module/plugins/hoster/RehostTo.py
deleted file mode 100644
index 9c07364ec..000000000
--- a/module/plugins/hoster/RehostTo.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import urllib
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class RehostTo(MultiHoster):
- __name__ = "RehostTo"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'https?://.*rehost\.to\..+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Rehost.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org")]
-
-
- def handlePremium(self, pyfile):
- self.download("http://rehost.to/process_download.php",
- get={'user': "cookie",
- 'pass': self.account.getAccountInfo(self.user)['session'],
- 'dl' : pyfile.url},
- disposition=True)
-
-
-getInfo = create_getInfo(RehostTo)
diff --git a/module/plugins/hoster/RemixshareCom.py b/module/plugins/hoster/RemixshareCom.py
deleted file mode 100644
index d60101aed..000000000
--- a/module/plugins/hoster/RemixshareCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://remixshare.com/download/z8uli
-#
-# 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.05"
-
- __pattern__ = r'https?://remixshare\.com/(download|dl)/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Remixshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de" ),
- ("Walter Purcaro", "vuolter@gmail.com" ),
- ("sraedler" , "simon.raedler@yahoo.de")]
-
-
- INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'>&nbsp;\((?P<S>\d+)&nbsp;(?P<U>[\w^_]+)\)<'
- HASHSUM_PATTERN = r'>(?P<T>MD5): (?P<H>\w+)'
- OFFLINE_PATTERN = r'<h1>Ooops!'
-
- LINK_PATTERN = r'var uri = "(.+?)"'
- TOKEN_PATTERN = r'var acc = (\d+)'
-
- WAIT_PATTERN = r'var XYZ = "(\d+)"'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- b = re.search(self.LINK_PATTERN, self.html)
- if not b:
- self.error(_("File url"))
-
- c = re.search(self.TOKEN_PATTERN, self.html)
- if not c:
- self.error(_("File token"))
-
- self.link = b.group(1) + "/zzz/" + c.group(1)
-
-
-getInfo = create_getInfo(RemixshareCom)
diff --git a/module/plugins/hoster/RgHostNet.py b/module/plugins/hoster/RgHostNet.py
deleted file mode 100644
index 1a45def44..000000000
--- a/module/plugins/hoster/RgHostNet.py
+++ /dev/null
@@ -1,28 +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.04"
-
- __pattern__ = r'http://(?:www\.)?rghost\.(net|ru)/[\d-]+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """RgHost.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
- INFO_PATTERN = r'data-share42-text="(?P<N>.+?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- HASHSUM_PATTERN = r'<dt>(?P<T>\w+)</dt>\s*<dd>(?P<H>\w+)'
- OFFLINE_PATTERN = r'>(File is deleted|page not found)'
-
- LINK_FREE_PATTERN = r'<a href="(.+?)" class="btn large'
-
-
-getInfo = create_getInfo(RgHostNet)
diff --git a/module/plugins/hoster/SafesharingEu.py b/module/plugins/hoster/SafesharingEu.py
deleted file mode 100644
index 08612e413..000000000
--- a/module/plugins/hoster/SafesharingEu.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class SafesharingEu(XFSHoster):
- __name__ = "SafesharingEu"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?safesharing\.eu/\w{12}'
-
- __description__ = """Safesharing.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- ERROR_PATTERN = r'(?:<div class="alert alert-danger">)(.+?)(?:</div>)'
-
-
-getInfo = create_getInfo(SafesharingEu)
diff --git a/module/plugins/hoster/SecureUploadEu.py b/module/plugins/hoster/SecureUploadEu.py
deleted file mode 100644
index 6bfbce328..000000000
--- a/module/plugins/hoster/SecureUploadEu.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class SecureUploadEu(XFSHoster):
- __name__ = "SecureUploadEu"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?secureupload\.eu/\w{12}'
-
- __description__ = """SecureUpload.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("z00nx", "z00nx0@gmail.com")]
-
-
- INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>'
-
-
-getInfo = create_getInfo(SecureUploadEu)
diff --git a/module/plugins/hoster/SendspaceCom.py b/module/plugins/hoster/SendspaceCom.py
deleted file mode 100644
index 0ab20949d..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.17"
-
- __pattern__ = r'https?://(?:www\.)?sendspace\.com/file/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Sendspace.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<h2 class="bgray">\s*<(?:b|strong)>(?P<N>[^<]+)</'
- SIZE_PATTERN = r'<div class="file_description reverse margin_center">\s*<b>File Size:</b>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)\s*</div>'
- OFFLINE_PATTERN = r'<div class="msg error" style="cursor: default">Sorry, the file you requested is not available.</div>'
-
- LINK_FREE_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, pyfile):
- params = {}
- for _i in xrange(3):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- if 'captcha_hash' in params:
- self.correctCaptcha()
- self.link = 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(pyfile.url, post=params)
- else:
- self.fail(_("Download link not found"))
-
-
-getInfo = create_getInfo(SendspaceCom)
diff --git a/module/plugins/hoster/Share4WebCom.py b/module/plugins/hoster/Share4WebCom.py
deleted file mode 100644
index 7a276c1fe..000000000
--- a/module/plugins/hoster/Share4WebCom.py
+++ /dev/null
@@ -1,22 +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.11"
-
- __pattern__ = r'https?://(?:www\.)?share4web\.com/get/\w+'
-
- __description__ = """Share4web.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "share4web.com"
-
-
-getInfo = create_getInfo(Share4WebCom)
diff --git a/module/plugins/hoster/Share76Com.py b/module/plugins/hoster/Share76Com.py
deleted file mode 100644
index 01bbc5ac2..000000000
--- a/module/plugins/hoster/Share76Com.py
+++ /dev/null
@@ -1,19 +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}'
- __config__ = []
-
- __description__ = """Share76.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
-getInfo = create_getInfo(Share76Com)
diff --git a/module/plugins/hoster/ShareFilesCo.py b/module/plugins/hoster/ShareFilesCo.py
deleted file mode 100644
index 2fd1fc7cf..000000000
--- a/module/plugins/hoster/ShareFilesCo.py
+++ /dev/null
@@ -1,19 +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}'
- __config__ = []
-
- __description__ = """Sharefiles.co hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(ShareFilesCo)
diff --git a/module/plugins/hoster/SharebeesCom.py b/module/plugins/hoster/SharebeesCom.py
deleted file mode 100644
index 347ae97e6..000000000
--- a/module/plugins/hoster/SharebeesCom.py
+++ /dev/null
@@ -1,19 +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}'
- __config__ = []
-
- __description__ = """ShareBees hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 505222fce..000000000
--- a/module/plugins/hoster/ShareonlineBiz.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urllib
-import urlparse
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class ShareonlineBiz(SimpleHoster):
- __name__ = "ShareonlineBiz"
- __type__ = "hoster"
- __version__ = "0.49"
-
- __pattern__ = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download\.php\?id=|dl/)(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Shareonline.biz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("mkaay", "mkaay@mkaay.de"),
- ("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", "http://www.share-online.biz/dl/\g<ID>")]
-
- CHECK_TRAFFIC = True
-
- RECAPTCHA_KEY = "6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX"
-
- ERROR_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>'
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = {'name': urlparse.urlparse(urllib.unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 3 if url else 1, 'url': url}
-
- if url:
- info['pattern'] = re.match(cls.__pattern__, url).groupdict()
-
- field = getURL("http://api.share-online.biz/linkcheck.php",
- get={'md5': "1"},
- post={'links': info['pattern']['ID']},
- decode=True).split(";")
-
- if field[1] == "OK":
- info['fileid'] = field[0]
- info['status'] = 2
- info['name'] = field[2]
- info['size'] = field[3] #: in bytes
- info['md5'] = field[4].strip().lower().replace("\n\n", "") #: md5
-
- elif field[1] in ("DELETED", "NOT FOUND"):
- info['status'] = 1
-
- return info
-
-
- def setup(self):
- self.resumeDownload = self.premium
- self.multiDL = False
-
-
- def handleCaptcha(self):
- recaptcha = ReCaptcha(self)
-
- for _i in xrange(5):
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- m = re.search(r'var wait=(\d+);', self.html)
- self.setWait(int(m.group(1)) if m else 30)
-
- res = self.load("%s/free/captcha/%d" % (self.pyfile.url, int(time.time() * 1000)),
- post={'dl_free' : "1",
- 'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
- if not res == '0':
- self.correctCaptcha()
- return res
- else:
- self.invalidCaptcha()
- else:
- self.invalidCaptcha()
- self.fail(_("No valid captcha solution received"))
-
-
- def handleFree(self, pyfile):
- self.wait(3)
-
- self.html = self.load("%s/free/" % pyfile.url,
- post={'dl_free': "1", 'choice': "free"},
- decode=True)
-
- self.checkErrors()
-
- res = self.handleCaptcha()
- self.link = res.decode('base64')
-
- if not self.link.startswith("http://"):
- self.error(_("Wrong download url"))
-
- self.wait()
-
-
- def checkFile(self, rules={}):
- 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"))
-
- return super(ShareonlineBiz, self).checkFile(rules)
-
-
- def handlePremium(self, pyfile): #: should be working better loading (account) api internally
- html = self.load("http://api.share-online.biz/account.php",
- get={'username': self.user,
- 'password': self.account.getAccountData(self.user)['password'],
- 'act' : "download",
- 'lid' : self.info['fileid']})
-
- self.api_data = dlinfo = {}
-
- for line in html.splitlines():
- key, value = line.split(": ")
- dlinfo[key.lower()] = value
-
- self.logDebug(dlinfo)
-
- if not dlinfo['status'] == "online":
- self.offline()
- else:
- pyfile.name = dlinfo['name']
- pyfile.size = int(dlinfo['size'])
-
- self.link = dlinfo['url']
-
- if self.link == "server_under_maintenance":
- self.tempOffline()
- else:
- self.multiDL = True
-
-
- def checkErrors(self):
- m = re.search(r"/failure/(.*?)/1", self.req.lastEffectiveURL)
- if m is None:
- self.info.pop('error', None)
- return
-
- errmsg = m.group(1).lower()
-
- try:
- self.logError(errmsg, re.search(self.ERROR_PATTERN, self.html).group(1))
- except Exception:
- self.logError("Unknown error occurred", errmsg)
-
- if errmsg is "invalid":
- self.fail(_("File not available"))
-
- elif errmsg in ("freelimit", "size", "proxy"):
- self.fail(_("Premium account needed"))
-
- elif errmsg in ("expired", "server"):
- self.retry(wait_time=600, reason=errmsg)
-
- elif 'slot' in errmsg:
- self.wantReconnect = True
- self.retry(24, 3600, errmsg)
-
- else:
- self.wantReconnect = True
- self.retry(wait_time=60, reason=errmsg)
-
-
-getInfo = create_getInfo(ShareonlineBiz)
diff --git a/module/plugins/hoster/ShareplaceCom.py b/module/plugins/hoster/ShareplaceCom.py
deleted file mode 100644
index babd0d1d4..000000000
--- a/module/plugins/hoster/ShareplaceCom.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.plugins.Hoster import Hoster
-
-
-class ShareplaceCom(Hoster):
- __name__ = "ShareplaceCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?shareplace\.(com|org)/\?\w+'
-
- __description__ = """Shareplace.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("ACCakut", 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.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 = urllib.unquote(
- url.replace("http://http:/", "").replace("vvvvvvvvv", "").replace("lllllllll", "").replace(
- "teletubbies", ""))
- self.logDebug("URL: %s" % url)
- return url
- else:
- self.error(_("Absolute filepath not found"))
-
-
- 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):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/SharingmatrixCom.py b/module/plugins/hoster/SharingmatrixCom.py
deleted file mode 100644
index 925c6af8d..000000000
--- a/module/plugins/hoster/SharingmatrixCom.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SharingmatrixCom(DeadHoster):
- __name__ = "SharingmatrixCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sharingmatrix\.com/file/\w+'
- __config__ = []
-
- __description__ = """Sharingmatrix.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("paulking", None)]
-
-
-getInfo = create_getInfo(SharingmatrixCom)
diff --git a/module/plugins/hoster/ShragleCom.py b/module/plugins/hoster/ShragleCom.py
deleted file mode 100644
index 9a4a259d8..000000000
--- a/module/plugins/hoster/ShragleCom.py
+++ /dev/null
@@ -1,20 +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>.+?)/'
- __config__ = []
-
- __description__ = """Cloudnator.com (Shragle.com) hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN", "RaNaN@pyload.org"),
- ("zoidberg", "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 b3f52bc8c..000000000
--- a/module/plugins/hoster/SimplyPremiumCom.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-from module.plugins.internal.SimpleHoster import secondsToMidnight
-
-
-class SimplyPremiumCom(MultiHoster):
- __name__ = "SimplyPremiumCom"
- __type__ = "hoster"
- __version__ = "0.08"
-
- __pattern__ = r'https?://.+simply-premium\.com'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Simply-Premium.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("EvolutionClip", "evolutionclip@live.de")]
-
-
- def setup(self):
- self.chunkLimit = 16
-
-
- def checkErrors(self):
- if '<valid>0</valid>' in self.html or (
- "You are not allowed to download from this host" in self.html and self.premium):
- self.account.relogin(self.user)
- self.retry()
-
- elif "NOTFOUND" in self.html:
- self.offline()
-
- elif "downloadlimit" in self.html:
- self.logWarning(_("Reached maximum connctions"))
- self.retry(5, 60, _("Reached maximum connctions"))
-
- elif "trafficlimit" in self.html:
- self.logWarning(_("Reached daily limit for this host"))
- self.retry(wait_time=secondsToMidnight(gmt=2), reason="Daily limit for this host reached")
-
- elif "hostererror" in self.html:
- self.logWarning(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
- self.retry(5, 60, _("Hoster is temporarily unavailable"))
-
-
- def handlePremium(self, pyfile):
- for i in xrange(5):
- self.html = self.load("http://www.simply-premium.com/premium.php", get={'info': "", 'link': self.pyfile.url})
-
- if self.html:
- self.logDebug("JSON data: " + self.html)
- break
- else:
- self.logInfo(_("Unable to get API data, waiting 1 minute and retry"))
- self.retry(5, 60, _("Unable to get API data"))
-
- self.checkErrors()
-
- try:
- self.pyfile.name = re.search(r'<name>([^<]+)</name>', self.html).group(1)
-
- except AttributeError:
- self.pyfile.name = ""
-
- try:
- self.pyfile.size = re.search(r'<size>(\d+)</size>', self.html).group(1)
-
- except AttributeError:
- self.pyfile.size = 0
-
- try:
- self.link = re.search(r'<download>([^<]+)</download>', self.html).group(1)
-
- except AttributeError:
- self.link = 'http://www.simply-premium.com/premium.php?link=' + self.pyfile.url
-
-
-getInfo = create_getInfo(SimplyPremiumCom)
diff --git a/module/plugins/hoster/SimplydebridCom.py b/module/plugins/hoster/SimplydebridCom.py
deleted file mode 100644
index aae71d983..000000000
--- a/module/plugins/hoster/SimplydebridCom.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo, replace_patterns
-
-
-class SimplydebridCom(MultiHoster):
- __name__ = "SimplydebridCom"
- __type__ = "hoster"
- __version__ = "0.17"
-
- __pattern__ = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd\.php'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Simply-debrid.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "kagenoshin@gmx.ch")]
-
-
- def handlePremium(self, pyfile):
- #fix the links for simply-debrid.com!
- self.link = replace_patterns(pyfile.url, [("clz.to", "cloudzer.net/file")
- ("http://share-online", "http://www.share-online")
- ("ul.to", "uploaded.net/file")
- ("uploaded.com", "uploaded.net")
- ("filerio.com", "filerio.in")
- ("lumfile.com", "lumfile.se")])
-
- if 'fileparadox' in self.link:
- self.link = self.link.replace("http://", "https://")
-
- self.html = self.load("http://simply-debrid.com/api.php", get={'dl': self.link})
- if 'tiger Link' in self.html or 'Invalid Link' in self.html or ('API' in self.html and 'ERROR' in self.html):
- self.error(_("Unable to unrestrict link"))
-
- self.link = self.html
-
- self.wait(5)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({"error": "No address associated with hostname"}):
- self.retry(24, 3 * 60, _("Bad file downloaded"))
-
- return super(SimplydebridCom, self).checkFile(rules)
-
-
-getInfo = create_getInfo(SimplydebridCom)
diff --git a/module/plugins/hoster/SizedriveCom.py b/module/plugins/hoster/SizedriveCom.py
deleted file mode 100644
index 3b9748fb6..000000000
--- a/module/plugins/hoster/SizedriveCom.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SizedriveCom(SimpleHoster):
- __name__ = "SizedriveCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sizedrive\.com/[rd]/(?P<ID>\w+)'
-
- __description__ = """Sizedrive.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("GammaC0de", None)]
-
-
- NAME_PATTERN = r'>Nome:</b> (?P<N>.+?)<'
- SIZE_PATTERN = r'>Tamanho:</b>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'ARQUIVO DELATADO POR'
-
-
- def setup(self):
- self.resumeDownload = False
- self.multiDL = False
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- self.wait(5)
- self.html = self.load("http://www.sizedrive.com/getdownload.php",
- post={'id': self.info['pattern']['ID']})
-
- m = re.search(r'<span id="boton_download" ><a href="(.+?)"', self.html)
- if m:
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(SizedriveCom)
diff --git a/module/plugins/hoster/SmoozedCom.py b/module/plugins/hoster/SmoozedCom.py
deleted file mode 100644
index 9ae12f145..000000000
--- a/module/plugins/hoster/SmoozedCom.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class SmoozedCom(MultiHoster):
- __name__ = "SmoozedCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.coreReady
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Smoozed.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("", "")]
-
-
- def handleFree(self, pyfile):
- # 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)
-
- # Check the link
- get_data = {'session_key': self.account.getAccountInfo(self.user)['session'],
- 'url' : pyfile.url}
-
- data = json_loads(self.load("http://www2.smoozed.com/api/check", get=get_data))
-
- if data["state"] != "ok":
- self.fail(data["message"])
-
- if data["data"].get("state", "ok") != "ok":
- if data["data"] == "Offline":
- self.offline()
- else:
- self.fail(data["data"]["message"])
-
- pyfile.name = data["data"]["name"]
- pyfile.size = int(data["data"]["size"])
-
- # Start the download
- header = self.load("http://www2.smoozed.com/api/download", get=get_data, just_header=True)
-
- if not "location" in header:
- self.fail(_("Unable to initialize download"))
- else:
- self.link = header["location"][-1] if isinstance(header["location"], list) else header["location"]
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'error': '{"state":"error"}',
- 'retry': '{"state":"retry"}'}):
- self.fail(_("Error response received"))
-
- return super(SmoozedCom, self).checkFile(rules)
diff --git a/module/plugins/hoster/SockshareCom.py b/module/plugins/hoster/SockshareCom.py
deleted file mode 100644
index 574c66d5b..000000000
--- a/module/plugins/hoster/SockshareCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SockshareCom(DeadHoster):
- __name__ = "SockshareCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?sockshare\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
- __config__ = []
-
- __description__ = """Sockshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("stickell", "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
-getInfo = create_getInfo(SockshareCom)
diff --git a/module/plugins/hoster/SolidfilesCom.py b/module/plugins/hoster/SolidfilesCom.py
deleted file mode 100644
index d359577d6..000000000
--- a/module/plugins/hoster/SolidfilesCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www.solidfiles.com/d/609cdb4b1b
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SolidfilesCom(SimpleHoster):
- __name__ = "SolidfilesCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?solidfiles\.com\/d/\w+'
-
- __description__ = """Solidfiles.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("sraedler", "simon.raedler@yahoo.de")]
-
-
- NAME_PATTERN = r'<h1 title="(?P<N>.+?)"'
- SIZE_PATTERN = r'<p class="meta">(?P<S>[\d.,]+) (?P<U>[\w_^]+)'
- OFFLINE_PATTERN = r'<h1>404'
-
- LINK_FREE_PATTERN = r'id="ddl-text" href="(.+?)"'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
-getInfo = create_getInfo(SolidfilesCom)
diff --git a/module/plugins/hoster/SoundcloudCom.py b/module/plugins/hoster/SoundcloudCom.py
deleted file mode 100644
index 8dff4f42a..000000000
--- a/module/plugins/hoster/SoundcloudCom.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.common.json_layer import json_loads
-
-
-class SoundcloudCom(SimpleHoster):
- __name__ = "SoundcloudCom"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'https?://(?:www\.)?soundcloud\.com/[\w-]+/[\w-]+'
- __config__ = [("use_premium", "bool" , "Use premium account if available", True ),
- ("quality" , "Lower;Higher", "Quality" , "Higher")]
-
- __description__ = """SoundCloud.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'title" content="(?P<N>.+?)"'
- OFFLINE_PATTERN = r'<title>"SoundCloud - Hear the world’s sounds"</title>'
-
-
- def handleFree(self, pyfile):
- try:
- song_id = re.search(r'sounds:(\d+)"', self.html).group(1)
-
- except Exception:
- self.error(_("Could not find song id"))
-
- try:
- client_id = re.search(r'"clientID":"(.+?)"', self.html).group(1)
-
- except Exception:
- client_id = "b45b1aa10f1ac2941910a7f0d10f8e28"
-
- # url to retrieve the actual song url
- streams = json_loads(self.load("https://api.soundcloud.com/tracks/%s/streams" % song_id,
- get={'client_id': client_id}))
-
- regex = re.compile(r'[^\d]')
- http_streams = sorted([(key, value) for key, value in streams.iteritems() if key.startswith('http_')],
- key=lambda t: regex.sub(t[0], ''),
- reverse=True)
-
- self.logDebug("Streams found: %s" % (http_streams or "None"))
-
- if http_streams:
- stream_name, self.link = http_streams[0 if self.getConfig('quality') == "Higher" else -1]
- pyfile.name += '.' + stream_name.split('_')[1].lower()
-
-
-getInfo = create_getInfo(SoundcloudCom)
diff --git a/module/plugins/hoster/SpeedLoadOrg.py b/module/plugins/hoster/SpeedLoadOrg.py
deleted file mode 100644
index 08a87e455..000000000
--- a/module/plugins/hoster/SpeedLoadOrg.py
+++ /dev/null
@@ -1,19 +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+)'
- __config__ = []
-
- __description__ = """Speedload.org hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "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 d3d627505..000000000
--- a/module/plugins/hoster/SpeedfileCz.py
+++ /dev/null
@@ -1,19 +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/.+'
- __config__ = []
-
- __description__ = """Speedfile.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 91788b2c8..000000000
--- a/module/plugins/hoster/SpeedyshareCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SpeedyshareCom(SimpleHoster):
- __name__ = "SpeedyshareCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?(speedyshare\.com|speedy\.sh)/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Speedyshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'class=downloadfilename>(?P<N>.*)</span></td>'
- SIZE_PATTERN = r'class=sizetagtext>(?P<S>.*) (?P<U>[kKmM]?[iI]?[bB]?)</div>'
-
- OFFLINE_PATTERN = r'class=downloadfilenamenotfound>.*</span>'
-
- LINK_FREE_PATTERN = r'<a href=\'(.*)\'><img src=/gf/slowdownload\.png alt=\'Slow Download\' border=0'
-
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(SpeedyshareCom)
diff --git a/module/plugins/hoster/StorageTo.py b/module/plugins/hoster/StorageTo.py
deleted file mode 100644
index f9bba7822..000000000
--- a/module/plugins/hoster/StorageTo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class StorageTo(DeadHoster):
- __name__ = "StorageTo"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?storage\.to/get/.+'
- __config__ = []
-
- __description__ = """Storage.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mkaay", "mkaay@mkaay.de")]
-
-
-getInfo = create_getInfo(StorageTo)
diff --git a/module/plugins/hoster/StreamCz.py b/module/plugins/hoster/StreamCz.py
deleted file mode 100644
index 97bed8109..000000000
--- a/module/plugins/hoster/StreamCz.py
+++ /dev/null
@@ -1,71 +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.20"
-
- __pattern__ = r'https?://(?:www\.)?stream\.cz/[^/]+/\d+'
-
- __description__ = """Stream.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- 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.resumeDownload = True
- self.multiDL = 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.error(_("CDN_PATTERN not found"))
- 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.NAME_PATTERN, self.html)
- if m is None:
- self.error(_("NAME_PATTERN not found"))
- 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") % 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 54f430508..000000000
--- a/module/plugins/hoster/StreamcloudEu.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class StreamcloudEu(XFSHoster):
- __name__ = "StreamcloudEu"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?streamcloud\.eu/\w{12}'
-
- __description__ = """Streamcloud.eu hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("seoester", "seoester@googlemail.com")]
-
-
- WAIT_PATTERN = r'var count = (\d+)'
-
- LINK_PATTERN = r'file: "(http://(stor|cdn)\d+\.streamcloud\.eu:?\d*/.*/video\.(mp4|flv))",'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = self.premium
-
-
-getInfo = create_getInfo(StreamcloudEu)
diff --git a/module/plugins/hoster/TurbobitNet.py b/module/plugins/hoster/TurbobitNet.py
deleted file mode 100644
index 7995bd0c0..000000000
--- a/module/plugins/hoster/TurbobitNet.py
+++ /dev/null
@@ -1,168 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import binascii
-import pycurl
-import random
-import re
-import time
-import urllib
-
-from Crypto.Cipher import ARC4
-
-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.19"
-
- __pattern__ = r'http://(?:www\.)?turbobit\.net/(?:download/free/)?(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """ Turbobit.net hoster plugin """
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("prOq", None)]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", "http://turbobit.net/\g<ID>.html")]
-
- COOKIES = [("turbobit.net", "user_lang", "en")]
-
- NAME_PATTERN = r'id="file-title">(?P<N>.+?)<'
- SIZE_PATTERN = r'class="file-size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File (?:was )?not found'
-
- LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'(/download/redirect/[^"\']+)'
-
- LIMIT_WAIT_PATTERN = r'<div id=\'timeout\'>(\d+)<'
- CAPTCHA_PATTERN = r'<img alt="Captcha" src="(.+?)"'
-
-
- def handleFree(self, pyfile):
- self.html = self.load("http://turbobit.net/download/free/%s" % self.info['pattern']['ID'],
- decode=True)
-
- rtUpdate = self.getRtUpdate()
-
- self.solveCaptcha()
-
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
-
- self.html = self.load(self.getDownloadUrl(rtUpdate))
-
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1)
-
-
- def solveCaptcha(self):
- for _i 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.error(_("Captcha form not found"))
- self.logDebug(inputs)
-
- if inputs['captcha_type'] == 'recaptcha':
- recaptcha = ReCaptcha(self)
- inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
- else:
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("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 '<div class="captcha-error">Incorrect, try again!<' 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.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)
- if m:
- url = "http://turbobit.net%s%s" % m.groups()
- else:
- url = "http://turbobit.net/files/timeout.js?ver=%s" % "".join(random.choice('0123456789ABCDEF') for _i 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.info['pattern']['ID'], b, urllib.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")
- else:
- self.retry()
-
- self.wait()
-
-
- def decrypt(self, data):
- cipher = ARC4.new(binascii.hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
- return binascii.unhexlify(cipher.encrypt(binascii.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)
-
-
-getInfo = create_getInfo(TurbobitNet)
diff --git a/module/plugins/hoster/TurbouploadCom.py b/module/plugins/hoster/TurbouploadCom.py
deleted file mode 100644
index 4b4d0c2e3..000000000
--- a/module/plugins/hoster/TurbouploadCom.py
+++ /dev/null
@@ -1,19 +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+)'
- __config__ = []
-
- __description__ = """Turboupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "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 6021a4c30..000000000
--- a/module/plugins/hoster/TusfilesNet.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.HTTPRequest import BadHeader
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class TusfilesNet(XFSHoster):
- __name__ = "TusfilesNet"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'https?://(?:www\.)?tusfiles\.net/\w{12}'
-
- __description__ = """Tusfiles.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("guidobelix", "guidobelix@hotmail.it")]
-
-
- INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)\['
- OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!|The file you are trying to download is no longer available'
-
-
- def setup(self):
- self.chunkLimit = -1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def downloadLink(self, link, disposition=True):
- try:
- return super(TusfilesNet, self).downloadLink(link, disposition)
-
- except BadHeader, e:
- if e.code is 503:
- self.multiDL = False
- raise Retry("503")
-
-
-getInfo = create_getInfo(TusfilesNet)
diff --git a/module/plugins/hoster/TwoSharedCom.py b/module/plugins/hoster/TwoSharedCom.py
deleted file mode 100644
index 086228637..000000000
--- a/module/plugins/hoster/TwoSharedCom.py
+++ /dev/null
@@ -1,33 +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.13"
-
- __pattern__ = r'http://(?:www\.)?2shared\.com/(account/)?(download|get|file|document|photo|video|audio)/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """2Shared.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<h1>(?P<N>.*)</h1>'
- SIZE_PATTERN = r'<span class="dtitle">File size:</span>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted\.'
-
- LINK_FREE_PATTERN = r'window.location =\'(.+?)\';'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
-
-
-getInfo = create_getInfo(TwoSharedCom)
diff --git a/module/plugins/hoster/UlozTo.py b/module/plugins/hoster/UlozTo.py
deleted file mode 100644
index b43ff8d92..000000000
--- a/module/plugins/hoster/UlozTo.py
+++ /dev/null
@@ -1,153 +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__ = "1.09"
-
- __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(?:live/)?(?P<ID>\w+/[^/?]*)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Uloz.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- INFO_PATTERN = r'<p>File <strong>(?P<N>[^<]+)</strong> is password protected</p>'
- NAME_PATTERN = r'<title>(?P<N>[^<]+) \| Uloz\.to</title>'
- SIZE_PATTERN = r'<span id="fileSize">.*?(?P<S>[\d.,]+\s[kMG]?B)</span>'
- OFFLINE_PATTERN = r'<title>404 - Page not found</title>|<h1 class="h1">File (has been deleted|was banned)</h1>'
-
- URL_REPLACEMENTS = [(r'(?<=http://)([^/]+)', "www.ulozto.net")]
- SIZE_REPLACEMENTS = [(r'([\d.]+)\s([kMG])B', convertDecimalPrefix)]
-
- CHECK_TRAFFIC = True
- DISPOSITION = False #: Remove in 0.4.10
-
- ADULT_PATTERN = r'<form action="(.+?)" method="post" id="frm-askAgeForm">'
- PASSWD_PATTERN = r'<div class="passwordProtectedFile">'
- VIPLINK_PATTERN = r'<a href=".+?\?disclaimer=1" class="linkVip">'
- TOKEN_PATTERN = r'<input type="hidden" name="_token_" .*?value="(.+?)"'
-
-
- def setup(self):
- self.chunkLimit = 16 if self.premium else 1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- action, inputs = self.parseHtmlForm('id="frm-downloadDialog-freeDownloadForm"')
- if not action or not inputs:
- self.error(_("Free download form not found"))
-
- 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.error(_("CAPTCHA form changed"))
-
- self.download("http://www.ulozto.net" + action, post=inputs)
-
-
- def handlePremium(self, pyfile):
- self.download(pyfile.url, get={'do': "directDownload"})
-
-
- def checkErrors(self):
- if re.search(self.ADULT_PATTERN, self.html):
- self.logInfo(_("Adult content confirmation needed"))
-
- m = re.search(self.TOKEN_PATTERN, self.html)
- if m is None:
- self.error(_("TOKEN_PATTERN not found"))
-
- self.html = self.load(pyfile.url,
- get={'do': "askAgeForm-submit"},
- post={"agree": "Confirm", "_token_": m.group(1)})
-
- if self.PASSWD_PATTERN in self.html:
- password = self.getPassword()
-
- if password:
- self.logInfo(_("Password protected link, trying ") + password)
- self.html = self.load(pyfile.url,
- get={'do': "passwordProtectedForm-submit"},
- post={"password": password, "password_send": 'Send'})
-
- if self.PASSWD_PATTERN in self.html:
- self.fail(_("Incorrect password"))
- else:
- self.fail(_("No password found"))
-
- if re.search(self.VIPLINK_PATTERN, self.html):
- self.html = self.load(pyfile.url, get={'disclaimer': "1"})
-
- return super(UlozTo, self).checkErrors()
-
-
- def checkFile(self, rules={}):
- 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.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"))
-
- return super(UlozTo, self).checkFile(rules)
-
-
-getInfo = create_getInfo(UlozTo)
diff --git a/module/plugins/hoster/UloziskoSk.py b/module/plugins/hoster/UloziskoSk.py
deleted file mode 100644
index d687ae7cb..000000000
--- a/module/plugins/hoster/UloziskoSk.py
+++ /dev/null
@@ -1,71 +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.25"
-
- __pattern__ = r'http://(?:www\.)?ulozisko\.sk/.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Ulozisko.sk hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<div class="down1">(?P<N>[^<]+)</div>'
- SIZE_PATTERN = ur'Veğkosť súboru: <strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong><br />'
- OFFLINE_PATTERN = ur'<span class = "red">ZadanÜ súbor neexistuje z jedného z nasledujúcich dÎvodov:</span>'
-
- LINK_FREE_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:
- self.link = "http://ulozisko.sk" + m.group(1)
- else:
- self.handleFree(pyfile)
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
- parsed_url = 'http://www.ulozisko.sk' + m.group(1)
-
- m = re.search(self.ID_PATTERN, self.html)
- if m is None:
- self.error(_("ID_PATTERN not found"))
- id = m.group(1)
-
- self.logDebug("URL:" + parsed_url + ' ID:' + id)
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("CAPTCHA_PATTERN not found"))
- 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" : 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 d090c8e7d..000000000
--- a/module/plugins/hoster/UnibytesCom.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UnibytesCom(SimpleHoster):
- __name__ = "UnibytesCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'https?://(?:www\.)?unibytes\.com/[\w .-]{11}B'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """UniBytes.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- HOSTER_DOMAIN = "unibytes.com"
-
- INFO_PATTERN = r'<span[^>]*?id="fileName".*?>(?P<N>[^>]+)</span>\s*\((?P<S>\d.*?)\)'
-
- WAIT_PATTERN = r'Wait for <span id="slowRest">(\d+)</span> sec'
- LINK_FREE_PATTERN = r'<a href="(.+?)">Download</a>'
-
-
- def handleFree(self, pyfile):
- domain = "http://www.%s/" % self.HOSTER_DOMAIN
- action, post_data = self.parseHtmlForm('id="startForm"')
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
-
- for _i in xrange(8):
- self.logDebug(action, post_data)
- self.html = self.load(urlparse.urljoin(domain, action), post=post_data)
-
- m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I)
- if m:
- self.link = 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_FREE_PATTERN, self.html)
- if m:
- self.link = 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(m.group(1) if m else 60, False)
-
- elif last_step in ("captcha", "last"):
- post_data['captcha'] = self.decryptCaptcha(urlparse.urljoin(domain, "/captcha.jpg"))
-
- else:
- self.fail(_("No valid captcha code entered"))
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
-
-getInfo = create_getInfo(UnibytesCom)
diff --git a/module/plugins/hoster/UnrestrictLi.py b/module/plugins/hoster/UnrestrictLi.py
deleted file mode 100644
index d3854db31..000000000
--- a/module/plugins/hoster/UnrestrictLi.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UnrestrictLi(DeadHoster):
- __name__ = "UnrestrictLi"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'https?://(?:www\.)?(unrestrict|unr)\.li/dl/[\w^_]+'
-
- __description__ = """Unrestrict.li multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
-getInfo = create_getInfo(UnrestrictLi)
diff --git a/module/plugins/hoster/UpleaCom.py b/module/plugins/hoster/UpleaCom.py
deleted file mode 100644
index 9d460ef98..000000000
--- a/module/plugins/hoster/UpleaCom.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class UpleaCom(XFSHoster):
- __name__ = "UpleaCom"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'https?://(?:www\.)?uplea\.com/dl/\w{15}'
-
- __description__ = """Uplea.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Redleon", None),
- ("GammaC0de", None)]
-
-
- DISPOSITION = False #@TODO: Remove in 0.4.10
-
- HOSTER_DOMAIN = "uplea.com"
-
- SIZE_REPLACEMENTS = [('ko','KB'), ('mo','MB'), ('go','GB'), ('Ko','KB'), ('Mo','MB'), ('Go','GB')]
-
- NAME_PATTERN = r'<span class="gold-text">(?P<N>.+?)</span>'
- SIZE_PATTERN = r'<span class="label label-info agmd">(?P<S>[\d.,]+) (?P<U>[\w^_]+?)</span>'
- OFFLINE_PATTERN = r'>You followed an invalid or expired link'
-
- LINK_PATTERN = r'"(https?://\w+\.uplea\.com/anonym/.*?)"'
-
- PREMIUM_ONLY_PATTERN = r'You need to have a Premium subscription to download this file'
- WAIT_PATTERN = r'timeText: ?([\d.]+),'
- STEP_PATTERN = r'<a href="(/step/.+)">'
-
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = 1
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- m = re.search(self.STEP_PATTERN, self.html)
- if m is None:
- self.error(_("STEP_PATTERN not found"))
-
- self.html = self.load(urlparse.urljoin("http://uplea.com/", m.group(1)))
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.logDebug(_("Waiting %s seconds") % m.group(1))
- self.wait(m.group(1), True)
- self.retry()
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_PATTERN not found"))
-
- self.link = m.group(1)
- self.wait(15)
-
-
-getInfo = create_getInfo(UpleaCom)
diff --git a/module/plugins/hoster/UploadStationCom.py b/module/plugins/hoster/UploadStationCom.py
deleted file mode 100644
index 90c177d71..000000000
--- a/module/plugins/hoster/UploadStationCom.py
+++ /dev/null
@@ -1,20 +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>\w+)'
- __config__ = []
-
- __description__ = """UploadStation.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadStationCom)
diff --git a/module/plugins/hoster/UploadableCh.py b/module/plugins/hoster/UploadableCh.py
deleted file mode 100644
index 2907bd825..000000000
--- a/module/plugins/hoster/UploadableCh.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UploadableCh(SimpleHoster):
- __name__ = "UploadableCh"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'http://(?:www\.)?uploadable\.ch/file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Uploadable.ch hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.uploadable.ch/file/\g<ID>')]
-
- INFO_PATTERN = r'div id=\"file_name\" title=.*>(?P<N>.+)<span class=\"filename_normal\">\((?P<S>[\d.]+) (?P<U>\w+)\)</span><'
-
- OFFLINE_PATTERN = r'>(File not available|This file is no longer available)'
- TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
-
- WAIT_PATTERN = r'>Please wait.+?<'
-
- RECAPTCHA_KEY = "6LdlJuwSAAAAAPJbPIoUhyqOJd7-yrah5Nhim5S3"
-
-
- def handleFree(self, pyfile):
- # Click the "free user" button and wait
- a = self.load(pyfile.url, post={'downloadLink': "wait"}, decode=True)
- self.logDebug(a)
-
- self.wait(30)
-
- # Make the recaptcha appear and show it the pyload interface
- b = self.load(pyfile.url, post={'checkDownload': "check"}, decode=True)
- self.logDebug(b) #: Expected output: {"success":"showCaptcha"}
-
- recaptcha = ReCaptcha(self)
-
- response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
-
- # Submit the captcha solution
- self.load("http://www.uploadable.ch/checkReCaptcha.php",
- post={'recaptcha_challenge_field' : challenge,
- 'recaptcha_response_field' : response,
- 'recaptcha_shortencode_field': self.info['pattern']['ID']},
- decode=True)
-
- self.wait(3)
-
- # Get ready for downloading
- self.load(pyfile.url, post={'downloadLink': "show"}, decode=True)
-
- self.wait(3)
-
- # Download the file
- self.download(pyfile.url, post={'download': "normal"}, disposition=True)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'wait': re.compile("Please wait for")}):
- self.logInfo("Downloadlimit reached, please wait or reconnect")
- self.wait(60 * 60, True)
- self.retry()
-
- return super(UploadableCh, self).checkFile(rules)
-
-
-getInfo = create_getInfo(UploadableCh)
diff --git a/module/plugins/hoster/UploadboxCom.py b/module/plugins/hoster/UploadboxCom.py
deleted file mode 100644
index 5a3856c6b..000000000
--- a/module/plugins/hoster/UploadboxCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadboxCom(DeadHoster):
- __name__ = "Uploadbox"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?uploadbox\.com/files/.+'
- __config__ = []
-
- __description__ = """UploadBox.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadboxCom)
diff --git a/module/plugins/hoster/UploadedTo.py b/module/plugins/hoster/UploadedTo.py
deleted file mode 100644
index 75710ea8b..000000000
--- a/module/plugins/hoster/UploadedTo.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UploadedTo(SimpleHoster):
- __name__ = "UploadedTo"
- __type__ = "hoster"
- __version__ = "0.87"
-
- __pattern__ = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Uploaded.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- DISPOSITION = False
-
- API_KEY = "lhF2IeeprweDfu9ccWlxXVVypA5nA3EL"
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://uploaded.net/file/\g<ID>')]
-
- TEMP_OFFLINE_PATTERN = r'<title>uploaded\.net - Maintenance'
-
- LINK_PREMIUM_PATTERN = r'<div class="tfree".*\s*<form method="post" action="(.+?)"'
-
- WAIT_PATTERN = r'Current waiting period: <span>(\d+)'
- DL_LIMIT_ERROR = r'You have reached the max. number of possible free downloads for this hour'
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- info = super(UploadedTo, cls).apiInfo(url)
-
- for _i in xrange(5):
- html = getURL("http://uploaded.net/api/filemultiple",
- get={"apikey": cls.API_KEY, 'id_0': re.match(cls.__pattern__, url).group('ID')},
- decode=True)
-
- if html != "can't find request":
- api = html.split(",", 4)
- if api[0] == "online":
- info.update({'name': api[4].strip(), 'size': api[2], 'status': 2})
- else:
- info['status'] = 1
- break
- else:
- time.sleep(3)
-
- return info
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = self.premium
- self.chunkLimit = 1 # critical problems with more chunks
-
-
- def checkErrors(self):
- if 'var free_enabled = false;' in self.html:
- self.logError(_("Free-download capacities exhausted"))
- self.retry(24, 5 * 60)
-
- elif "limit-size" in self.html:
- self.fail(_("File too big for free download"))
-
- elif "limit-slot" in self.html: # Temporary restriction so just wait a bit
- self.wait(30 * 60, True)
- self.retry()
-
- elif "limit-parallel" in self.html:
- self.fail(_("Cannot download in parallel"))
-
- elif "limit-dl" in self.html or self.DL_LIMIT_ERROR in self.html: # limit-dl
- self.wait(3 * 60 * 60, True)
- self.retry()
-
- elif '"err":"captcha"' in self.html:
- self.invalidCaptcha()
-
- else:
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.wait(m.group(1))
-
-
- def handleFree(self, pyfile):
- self.load("http://uploaded.net/language/en", just_header=True)
-
- self.html = self.load("http://uploaded.net/js/download.js", decode=True)
-
- recaptcha = ReCaptcha(self)
- response, challenge = recaptcha.challenge()
-
- self.html = self.load("http://uploaded.net/io/ticket/captcha/%s" % self.info['pattern']['ID'],
- post={'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- if "type:'download'" in self.html:
- self.correctCaptcha()
- try:
- self.link = re.search("url:'(.+?)'", self.html).group(1)
-
- except Exception:
- pass
-
- self.checkErrors()
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({'limit-dl': self.DL_LIMIT_ERROR}):
- self.wait(3 * 60 * 60, True)
- self.retry()
-
- return super(UploadedTo, self).checkFile(rules)
-
-
-getInfo = create_getInfo(UploadedTo)
diff --git a/module/plugins/hoster/UploadhereCom.py b/module/plugins/hoster/UploadhereCom.py
deleted file mode 100644
index d3b095c15..000000000
--- a/module/plugins/hoster/UploadhereCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadhereCom(DeadHoster):
- __name__ = "UploadhereCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?uploadhere\.com/\w{10}'
- __config__ = []
-
- __description__ = """Uploadhere.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadhereCom)
diff --git a/module/plugins/hoster/UploadheroCom.py b/module/plugins/hoster/UploadheroCom.py
deleted file mode 100644
index 5c74f10eb..000000000
--- a/module/plugins/hoster/UploadheroCom.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://uploadhero.co/dl/wQBRAVSM
-
-import re
-import urlparse
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UploadheroCom(SimpleHoster):
- __name__ = "UploadheroCom"
- __type__ = "hoster"
- __version__ = "0.18"
-
- __pattern__ = r'http://(?:www\.)?uploadhero\.com?/dl/\w+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """UploadHero.co plugin"""
- __license__ = "GPLv3"
- __authors__ = [("mcmyst", "mcmyst@hotmail.fr"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'<div class="nom_de_fichier">(?P<N>.+?)<'
- SIZE_PATTERN = r'>Filesize: </span><strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'<p class="titre_dl_2">'
-
- 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\?\w+)"'
-
- LINK_FREE_PATTERN = r'var magicomfg = \'<a href="(.+?)"|"(http://storage\d+\.uploadhero\.co.+?)"'
- LINK_PREMIUM_PATTERN = r'<a href="(.+?)" id="downloadnow"'
-
-
- def handleFree(self, pyfile):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.error(_("Captcha not found"))
-
- captcha = self.decryptCaptcha(urlparse.urljoin("http://uploadhero.co", m.group(1)))
-
- self.html = self.load(pyfile.url,
- get={"code": captcha})
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m:
- self.link = m.group(1) or m.group(2)
- self.wait(50)
-
-
- def checkErrors(self):
- m = re.search(self.IP_BLOCKED_PATTERN, self.html)
- if m:
- self.html = self.load(urlparse.urljoin("http://uploadhero.co", 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()
-
- return super(UploadheroCom, self).checkErrors()
-
-
-getInfo = create_getInfo(UploadheroCom)
diff --git a/module/plugins/hoster/UploadingCom.py b/module/plugins/hoster/UploadingCom.py
deleted file mode 100644
index c2e0d2873..000000000
--- a/module/plugins/hoster/UploadingCom.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-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.40"
-
- __pattern__ = r'http://(?:www\.)?uploading\.com/files/(?:get/)?(?P<ID>\w+)'
-
- __description__ = """Uploading.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("mkaay", "mkaay@mkaay.de"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'id="file_title">(?P<N>.+)</'
- SIZE_PATTERN = r'size tip_container">(?P<S>[\d.,]+) (?P<U>[\w^_]+)<'
- OFFLINE_PATTERN = r'(Page|file) not found'
-
- COOKIES = [("uploading.com", "lang", "1"),
- (".uploading.com", "language", "1"),
- (".uploading.com", "setlang", "en"),
- (".uploading.com", "_lang", "en")]
-
-
- def process(self, pyfile):
- if not "/get/" in pyfile.url:
- pyfile.url = pyfile.url.replace("/files", "/files/get")
-
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
-
- if self.premium:
- self.handlePremium(pyfile)
- else:
- self.handleFree(pyfile)
-
-
- def handlePremium(self, pyfile):
- postData = {'action': 'get_link',
- 'code' : self.info['pattern']['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:
- self.link = url.group(1).replace("\\/", "/")
-
- raise Exception("Plugin defect")
-
-
- def handleFree(self, pyfile):
- m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html)
- if m:
- pyfile.error = m.group(1)
- self.logWarning(pyfile.error)
- self.retry(6, (6 * 60 if m.group(2) else 15) * 60, pyfile.error)
-
- ajax_url = "http://uploading.com/files/get/?ajax"
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
- self.req.http.lastURL = pyfile.url
-
- res = json_loads(self.load(ajax_url, post={'action': 'second_page', 'code': self.info['pattern']['ID']}))
-
- if 'answer' in res and 'wait_time' in res['answer']:
- wait_time = int(res['answer']['wait_time'])
- self.logInfo(_("Waiting %d seconds") % wait_time)
- self.wait(wait_time)
- else:
- self.error(_("No AJAX/WAIT"))
-
- res = json_loads(self.load(ajax_url, post={'action': 'get_link', 'code': self.info['pattern']['ID'], 'pass': 'false'}))
-
- if 'answer' in res and 'link' in res['answer']:
- url = res['answer']['link']
- else:
- self.error(_("No 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.error(_("No URL"))
-
- self.link = url
-
-
-getInfo = create_getInfo(UploadingCom)
diff --git a/module/plugins/hoster/UploadkingCom.py b/module/plugins/hoster/UploadkingCom.py
deleted file mode 100644
index 6c24fe51a..000000000
--- a/module/plugins/hoster/UploadkingCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadkingCom(DeadHoster):
- __name__ = "UploadkingCom"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'http://(?:www\.)?uploadking\.com/\w{10}'
- __config__ = []
-
- __description__ = """UploadKing.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
-getInfo = create_getInfo(UploadkingCom)
diff --git a/module/plugins/hoster/UpstoreNet.py b/module/plugins/hoster/UpstoreNet.py
deleted file mode 100644
index ec0c88c82..000000000
--- a/module/plugins/hoster/UpstoreNet.py
+++ /dev/null
@@ -1,73 +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.05"
-
- __pattern__ = r'https?://(?:www\.)?upstore\.net/'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Upstore.Net File Download Hoster"""
- __license__ = "GPLv3"
- __authors__ = [("igel", "igelkun@myopera.com")]
-
-
- 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_FREE_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>'
-
-
- def handleFree(self, pyfile):
- # STAGE 1: get link to continue
- m = re.search(self.CHASH_PATTERN, self.html)
- if m is None:
- self.error(_("CHASH_PATTERN not found"))
- chash = m.group(1)
- self.logDebug("Read hash " + chash)
- # continue to stage2
- post_data = {'hash': chash, 'free': 'Slow download'}
- self.html = self.load(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)
-
- # try the captcha 5 times
- for i in xrange(5):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m is None:
- self.error(_("Wait pattern not found"))
- wait_time = int(m.group(1))
-
- # then, do the waiting
- self.wait(wait_time)
-
- # then, handle the captcha
- response, challenge = recaptcha.challenge()
- post_data.update({'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field' : response})
-
- self.html = self.load(pyfile.url, post=post_data, decode=True)
-
- # STAGE 3: get direct link
- m = re.search(self.LINK_FREE_PATTERN, self.html, re.S)
- if m:
- break
-
- if m is None:
- self.error(_("Download link not found"))
-
- self.link = m.group(1)
-
-
-getInfo = create_getInfo(UpstoreNet)
diff --git a/module/plugins/hoster/UptoboxCom.py b/module/plugins/hoster/UptoboxCom.py
deleted file mode 100644
index 991bc640e..000000000
--- a/module/plugins/hoster/UptoboxCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class UptoboxCom(XFSHoster):
- __name__ = "UptoboxCom"
- __type__ = "hoster"
- __version__ = "0.18"
-
- __pattern__ = r'https?://(?:www\.)?(uptobox|uptostream)\.com/\w{12}'
-
- __description__ = """Uptobox.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- 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'>Service Unavailable'
-
- LINK_PATTERN = r'"(https?://\w+\.uptobox\.com/d/.*?)"'
-
- ERROR_PATTERN = r'>(You have to wait.+till next download.)<' #@TODO: Check XFSHoster ERROR_PATTERN
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
- self.resumeDownload = True
-
-
-getInfo = create_getInfo(UptoboxCom)
diff --git a/module/plugins/hoster/VeehdCom.py b/module/plugins/hoster/VeehdCom.py
deleted file mode 100644
index 78da91020..000000000
--- a/module/plugins/hoster/VeehdCom.py
+++ /dev/null
@@ -1,81 +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"""
- __license__ = "GPLv3"
- __authors__ = [("cat", "cat@pyload")]
-
-
- 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.logDebug("Requesting page: %s" % 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.error(_("Video title not found"))
-
- name = m.group(1)
-
- # replace unwanted characters in filename
- if self.getConfig('filename_spaces'):
- pattern = '[^\w ]+'
- else:
- pattern = '[^\w.]+'
-
- 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.error(_("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 57b24623b..000000000
--- a/module/plugins/hoster/VeohCom.py
+++ /dev/null
@@ -1,54 +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.22"
-
- __pattern__ = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)'
- __config__ = [("use_premium", "bool" , "Use premium account if available", True ),
- ("quality" , "Low;High;Auto", "Quality" , "Auto")]
-
- __description__ = """Veoh.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- NAME_PATTERN = r'<meta name="title" content="(?P<N>.*?)"'
- OFFLINE_PATTERN = r'>Sorry, we couldn\'t find the video you were looking for'
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'http://www.veoh.com/watch/\g<ID>')]
-
- COOKIES = [("veoh.com", "lassieLocale", "en")]
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = -1
-
-
- def handleFree(self, pyfile):
- 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:
- pyfile.name += ".mp4"
- self.link = m.group(1).replace("\\", "")
- 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 f1a32a897..000000000
--- a/module/plugins/hoster/VidPlayNet.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://vidplay.net/38lkev0h3jv0
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class VidPlayNet(XFSHoster):
- __name__ = "VidPlayNet"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'https?://(?:www\.)?vidplay\.net/\w{12}'
-
- __description__ = """VidPlay.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
-
-
- NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>'
-
-
-getInfo = create_getInfo(VidPlayNet)
diff --git a/module/plugins/hoster/VimeoCom.py b/module/plugins/hoster/VimeoCom.py
deleted file mode 100644
index a5196cb92..000000000
--- a/module/plugins/hoster/VimeoCom.py
+++ /dev/null
@@ -1,75 +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.04"
-
- __pattern__ = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)'
- __config__ = [("use_premium", "bool" , "Use premium account if available" , True ),
- ("quality" , "Lowest;Mobile;SD;HD;Highest", "Quality" , "Highest"),
- ("original" , "bool" , "Try to download the original file", True )]
-
- __description__ = """Vimeo.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- 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.<'
-
- URL_REPLACEMENTS = [(__pattern__ + ".*", r'https://www.vimeo.com/\g<ID>')]
-
- COOKIES = [("vimeo.com", "language", "en")]
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = True
- self.chunkLimit = -1
-
-
- def handleFree(self, pyfile):
- password = self.getPassword()
-
- if self.js and 'class="btn iconify_down_b"' in self.html:
- html = self.js.eval(self.load(pyfile.url, get={'action': "download", 'password': password}, decode=True))
- pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) '
- else:
- html = self.load("https://player.vimeo.com/video/" + self.info['pattern']['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.link = 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.link = 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 1a7ec2810..000000000
--- a/module/plugins/hoster/Vipleech4UCom.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class Vipleech4UCom(DeadHoster):
- __name__ = "Vipleech4UCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?vipleech4u\.com/manager\.php'
- __config__ = []
-
- __description__ = """Vipleech4u.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Kagenoshin", "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 a8ae99a1b..000000000
--- a/module/plugins/hoster/WarserverCz.py
+++ /dev/null
@@ -1,19 +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+'
- __config__ = []
-
- __description__ = """Warserver.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "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 59f61dc87..000000000
--- a/module/plugins/hoster/WebshareCz.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class WebshareCz(SimpleHoster):
- __name__ = "WebshareCz"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'https?://(?:www\.)?webshare\.cz/(?:#/)?file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """WebShare.cz hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it"),
- ("rush", "radek.senfeld@gmail.com")]
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = super(WebshareCz, cls).getInfo(url, html)
-
- if url:
- info['pattern'] = re.match(cls.__pattern__, url).groupdict()
-
- api_data = getURL("https://webshare.cz/api/file_info/",
- post={'ident': info['pattern']['ID']},
- decode=True)
-
- if 'File not found' in api_data:
- info['status'] = 1
- else:
- info["status"] = 2
- info['name'] = re.search('<name>(.+)</name>', api_data).group(1) or info['name']
- info['size'] = re.search('<size>(.+)</size>', api_data).group(1) or info['size']
-
- return info
-
-
- def handleFree(self, pyfile):
- wst = self.account.infos['wst'] if self.account and 'wst' in self.account.infos else ""
-
- api_data = getURL('https://webshare.cz/api/file_link/',
- post={'ident': self.info['pattern']['ID'], 'wst': wst},
- decode=True)
-
- self.logDebug("API data: " + api_data)
-
- m = re.search('<link>(.+)</link>', api_data)
- if m is None:
- self.error(_("Unable to detect direct link"))
-
- self.link = m.group(1)
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
-getInfo = create_getInfo(WebshareCz)
diff --git a/module/plugins/hoster/WrzucTo.py b/module/plugins/hoster/WrzucTo.py
deleted file mode 100644
index f11d03ea8..000000000
--- a/module/plugins/hoster/WrzucTo.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class WrzucTo(SimpleHoster):
- __name__ = "WrzucTo"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?wrzuc\.to/(\w+(\.wt|\.html)|(\w+/?linki/\w+))'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Wrzuc.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'id="file_info">\s*<strong>(?P<N>.*?)</strong>'
- SIZE_PATTERN = r'class="info">\s*<tr>\s*<td>(?P<S>.*?)</td>'
-
- COOKIES = [("wrzuc.to", "language", "en")]
-
-
- def setup(self):
- self.multiDL = True
-
-
- def handleFree(self, pyfile):
- data = dict(re.findall(r'(md5|file): "(.*?)"', self.html))
- if len(data) != 2:
- self.error(_("No file ID"))
-
- self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
- self.req.http.lastURL = pyfile.url
- self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']})
-
- self.req.http.lastURL = 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.error(_("No download URL"))
-
- self.link = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link'])
-
-
-getInfo = create_getInfo(WrzucTo)
diff --git a/module/plugins/hoster/WuploadCom.py b/module/plugins/hoster/WuploadCom.py
deleted file mode 100644
index 14c56aba3..000000000
--- a/module/plugins/hoster/WuploadCom.py
+++ /dev/null
@@ -1,20 +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/((\w+/)?\d+)(/.*)?'
- __config__ = []
-
- __description__ = """Wupload.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("Paul King", None)]
-
-
-getInfo = create_getInfo(WuploadCom)
diff --git a/module/plugins/hoster/X7To.py b/module/plugins/hoster/X7To.py
deleted file mode 100644
index 15e011720..000000000
--- a/module/plugins/hoster/X7To.py
+++ /dev/null
@@ -1,19 +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/'
- __config__ = []
-
- __description__ = """X7.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("ernieb", "ernieb")]
-
-
-getInfo = create_getInfo(X7To)
diff --git a/module/plugins/hoster/XFileSharingPro.py b/module/plugins/hoster/XFileSharingPro.py
deleted file mode 100644
index 1bfb504b7..000000000
--- a/module/plugins/hoster/XFileSharingPro.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.XFSHoster import XFSHoster, create_getInfo
-
-
-class XFileSharingPro(XFSHoster):
- __name__ = "XFileSharingPro"
- __type__ = "hoster"
- __version__ = "0.45"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """XFileSharingPro dummy hoster plugin for hook"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- URL_REPLACEMENTS = [("/embed-", "/")]
-
-
- def _log(self, type, args):
- msg = " | ".join(str(a).strip() for a in args if a)
- logger = getattr(self.log, type)
- logger("%s: %s: %s" % (self.__name__, self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
-
-
- def init(self):
- super(XFileSharingPro, self).init()
-
- self.__pattern__ = self.core.pluginManager.hosterPlugins[self.__name__]['pattern']
-
- self.HOSTER_DOMAIN = re.match(self.__pattern__, self.pyfile.url).group("DOMAIN").lower()
- self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
-
- account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
-
- if account and account.canUse():
- self.account = account
-
- elif self.account:
- self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
-
- else:
- return
-
- self.user, data = self.account.selectAccount()
- self.req = self.account.getAccountRequest(self.user)
- self.premium = self.account.isPremium(self.user)
-
-
- def setup(self):
- self.chunkLimit = 1
- self.resumeDownload = self.premium
- self.multiDL = True
-
-
-getInfo = create_getInfo(XFileSharingPro)
diff --git a/module/plugins/hoster/XHamsterCom.py b/module/plugins/hoster/XHamsterCom.py
deleted file mode 100644
index a1711cb0e..000000000
--- a/module/plugins/hoster/XHamsterCom.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-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"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
- 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.S)
- json_flashvar = flashvar_pattern.search(self.html)
-
- if not json_flashvar:
- self.error(_("flashvar not found"))
-
- j = clean_json(json_flashvar.group(1))
- flashvars = json_loads(j)
-
- if flashvars['srv']:
- srv_url = flashvars['srv'] + '/'
- else:
- self.error(_("srv_url not found"))
-
- if flashvars['url_mode']:
- url_mode = flashvars['url_mode']
-
-
- else:
- self.error(_("url_mode not found"))
-
- if self.desired_fmt == ".mp4":
- file_url = re.search(r"<a href=\"" + srv_url + "(.+?)\"", self.html)
- if file_url is None:
- self.error(_("file_url not found"))
- file_url = file_url.group(1)
- long_url = srv_url + file_url
- self.logDebug("long_url = " + long_url)
- else:
- if flashvars['file']:
- file_url = urllib.unquote(flashvars['file'])
- else:
- self.error(_("file_url not found"))
-
- if url_mode == '3':
- long_url = file_url
- self.logDebug("long_url = " + long_url)
- else:
- long_url = srv_url + "key=" + file_url
- self.logDebug("long_url = " + 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):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/XVideosCom.py b/module/plugins/hoster/XVideosCom.py
deleted file mode 100644
index a8f291824..000000000
--- a/module/plugins/hoster/XVideosCom.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.plugins.Hoster import Hoster
-
-
-class XVideosCom(Hoster):
- __name__ = "XVideos.com"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'http://(?:www\.)?xvideos\.com/video(\d+)'
-
- __description__ = """XVideos.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
-
- 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(urllib.unquote(re.search(r"flv_url=([^&]+)&", site).group(1)))
diff --git a/module/plugins/hoster/XdadevelopersCom.py b/module/plugins/hoster/XdadevelopersCom.py
deleted file mode 100644
index 45d1e92cb..000000000
--- a/module/plugins/hoster/XdadevelopersCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*
-#
-# Test links:
-# http://forum.xda-developers.com/devdb/project/dl/?id=10885
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class XdadevelopersCom(SimpleHoster):
- __name__ = "XdadevelopersCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?forum\.xda-developers\.com/devdb/project/dl/\?id=\d+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Xda-developers.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- NAME_PATTERN = r'<label>Filename:</label>\s*<div>\s*(?P<N>.*?)\n'
- SIZE_PATTERN = r'<label>Size:</label>\s*<div>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'</i> Device Filter</h3>'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- self.link = pyfile.url + "&task=get" #@TODO: Revert to `get={'task': "get"}` in 0.4.10
-
-
-getInfo = create_getInfo(XdadevelopersCom)
diff --git a/module/plugins/hoster/Xdcc.py b/module/plugins/hoster/Xdcc.py
deleted file mode 100644
index d167e4cab..000000000
--- a/module/plugins/hoster/Xdcc.py
+++ /dev/null
@@ -1,208 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import socket
-import struct
-import sys
-import time
-
-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"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "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 _i in xrange(0, 3):
- try:
- nmn = self.doDownload(pyfile.url)
- self.logDebug("Download of %s finished." % nmn)
- return
- except socket.error, e:
- if hasattr(e, "errno"):
- errno = e.errno
- else:
- errno = e.args[0]
-
- if errno == 10054:
- self.logDebug("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))
-
- self.setWait(3)
- self.wait()
-
- 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("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(_("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/YadiSk.py b/module/plugins/hoster/YadiSk.py
deleted file mode 100644
index 3b4c9d985..000000000
--- a/module/plugins/hoster/YadiSk.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import random
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class YadiSk(SimpleHoster):
- __name__ = "YadiSk"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://yadi\.sk/d/[\w-]+'
-
- __description__ = """Yadi.sk hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("GammaC0de", None)]
-
-
- OFFLINE_PATTERN = r'Nothing found'
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = super(YadiSk, cls).getInfo(url, html)
-
- if html:
- if 'idclient' not in info:
- info['idclient'] = ""
- for _i in xrange(32):
- info ['idclient'] += random.choice('0123456abcdef')
-
- m = re.search(r'<script id="models-client" type="application/json">(.+?)</script>', html)
- if m:
- api_data = json_loads(m.group(1))
- try:
- for sect in api_data:
- if 'model' in sect:
- if sect['model'] == "config":
- info['version'] = sect['data']['version']
- info['sk'] = sect['data']['sk']
-
- elif sect['model'] == "resource":
- info['id'] = sect['data']['id']
- info['size'] = sect['data']['meta']['size']
- info['name'] = sect['data']['name']
-
- except Exception, e:
- info['status'] = 8
- info['error'] = _("Unexpected server response: %s") % e.message
-
- else:
- info['status'] = 8
- info['error'] = _("could not find required json data")
-
- return info
-
-
- def setup(self):
- self.resumeDownload = False
- self.multiDL = False
- self.chunkLimit = 1
-
-
- def handleFree(self, pyfile):
- if any(True for _k in ['id', 'sk', 'version', 'idclient'] if _k not in self.info):
- self.error(_("Missing JSON data"))
-
-
- try:
- self.html = self.load("https://yadi.sk/models/",
- get={'_m': "do-get-resource-url"},
- post={'idClient': self.info['idclient'],
- 'version' : self.info['version'],
- '_model.0': "do-get-resource-url",
- 'sk' : self.info['sk'],
- 'id.0' : self.info['id']})
-
- self.link = json_loads(self.html)['models'][0]['data']['file']
-
- except Exception:
- pass
-
-
-getInfo = create_getInfo(YadiSk)
diff --git a/module/plugins/hoster/YibaishiwuCom.py b/module/plugins/hoster/YibaishiwuCom.py
deleted file mode 100644
index 7ca6e1ac0..000000000
--- a/module/plugins/hoster/YibaishiwuCom.py
+++ /dev/null
@@ -1,59 +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.14"
-
- __pattern__ = r'http://(?:www\.)?(?:u\.)?115\.com/file/(?P<ID>\w+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """115.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- NAME_PATTERN = r'file_name: \'(?P<N>.+?)\''
- SIZE_PATTERN = r'file_size: \'(?P<S>.+?)\''
- OFFLINE_PATTERN = ur'<h3><i style="color:red;">哎呀提取码䞍存圚䞍劚搜搜看吧</i></h3>'
-
- LINK_FREE_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)'
-
-
- def handleFree(self, pyfile):
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_FREE_PATTERN not found"))
-
- url = m.group(1)
-
- self.logDebug(('FREEUSER' if m.group(2) == 'download' else 'GUEST') + ' URL', url)
-
- res = json_loads(self.load("http://115.com" + url, decode=False))
- if "urls" in res:
- mirrors = res['urls']
-
- elif "data" in res:
- mirrors = res['data']
-
- else:
- mirrors = None
-
- for mr in mirrors:
- try:
- self.link = mr['url'].replace("\\", "")
- self.logDebug("Trying URL: " + self.link)
- break
- except Exception:
- 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 19d07fa36..000000000
--- a/module/plugins/hoster/YoupornCom.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class YoupornCom(Hoster):
- __name__ = "YoupornCom"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'http://(?:www\.)?youporn\.com/watch/.+'
-
- __description__ = """Youporn.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("willnix", "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>(.+) - '
- 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):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/YourfilesTo.py b/module/plugins/hoster/YourfilesTo.py
deleted file mode 100644
index d0316d3ac..000000000
--- a/module/plugins/hoster/YourfilesTo.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import reimport urllib
-
-from module.plugins.Hoster import Hoster
-
-
-class YourfilesTo(Hoster):
- __name__ = "YourfilesTo"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?yourfiles\.(to|biz)/\?d=\w+'
-
- __description__ = """Youfiles.to hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("jeix", "jeix@hasnomail.de"),
- ("skydancer", "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.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 = urllib.unquote(url.replace("http://http:/http://", "http://").replace("dumdidum", ""))
- return url
- else:
- self.error(_("Absolute filepath not found"))
-
-
- 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):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/YoutubeCom.py b/module/plugins/hoster/YoutubeCom.py
deleted file mode 100644
index e2ab146a9..000000000
--- a/module/plugins/hoster/YoutubeCom.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import subprocessimport urllib
-
-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"""
-
- isExe = lambda x: os.path.isfile(x) and os.access(x, os.X_OK)
-
- fpath, fname = os.path.split(program)
-
- if fpath:
- if isExe(program):
- return program
- else:
- for path in os.environ['PATH'].split(os.pathsep):
- path = path.strip('"')
- exe_file = os.path.join(path, program)
- if isExe(exe_file):
- return exe_file
-
-
-class YoutubeCom(Hoster):
- __name__ = "YoutubeCom"
- __type__ = "hoster"
- __version__ = "0.41"
-
- __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 (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"""
- __license__ = "GPLv3"
- __authors__ = [("spoob", "spoob@pyload.org"),
- ("zoidberg", "zoidberg@mujmail.cz")]
-
-
- 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 = True
- self.multiDL = True
-
-
- def process(self, pyfile):
- pyfile.url = replace_patterns(pyfile.url, self.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 not desired_fmt:
- desired_fmt = quality.get(self.getConfig('quality'), 18)
-
- elif desired_fmt not in self.formats:
- self.logWarning(_("FMT %d unknown, using default") % desired_fmt)
- desired_fmt = 0
-
- #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']), urllib.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 8d3de5b26..000000000
--- a/module/plugins/hoster/ZDF.py
+++ /dev/null
@@ -1,59 +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.80"
-
- __pattern__ = r'http://(?:www\.)?zdf\.de/ZDFmediathek/\D*(\d+)\D*'
-
- __description__ = """ZDF.de hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = []
-
- 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"\D*(\d{4,})\D*", 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/ZShareNet.py b/module/plugins/hoster/ZShareNet.py
deleted file mode 100644
index 8142136bb..000000000
--- a/module/plugins/hoster/ZShareNet.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class ZShareNet(DeadHoster):
- __name__ = "ZShareNet"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'https?://(?:ww[2w]\.)?zshares?\.net/.+'
- __config__ = []
-
- __description__ = """ZShare.net hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("espes", None),
- ("Cptn Sandwich", None)]
-
-
-getInfo = create_getInfo(ZShareNet)
diff --git a/module/plugins/hoster/ZeveraCom.py b/module/plugins/hoster/ZeveraCom.py
deleted file mode 100644
index 617e00e58..000000000
--- a/module/plugins/hoster/ZeveraCom.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.internal.MultiHoster import MultiHoster, create_getInfo
-
-
-class ZeveraCom(MultiHoster):
- __name__ = "ZeveraCom"
- __type__ = "hoster"
- __version__ = "0.29"
-
- __pattern__ = r'https?://(?:www\.)zevera\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Zevera.com multi-hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- def handlePremium(self, pyfile):
- self.link = "https://%s/getFiles.ashx?ourl=%s" % (self.account.HOSTER_DOMAIN, pyfile.url)
-
-
- def checkFile(self, rules={}):
- if self.checkDownload({"error": 'action="ErrorDownload.aspx'}):
- self.fail(_("Error response received"))
-
- return super(ZeveraCom, self).checkFile(rules)
-
-
-getInfo = create_getInfo(ZeveraCom)
diff --git a/module/plugins/hoster/ZippyshareCom.py b/module/plugins/hoster/ZippyshareCom.py
deleted file mode 100644
index c47ac4fe1..000000000
--- a/module/plugins/hoster/ZippyshareCom.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from BeautifulSoup import BeautifulSoup
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class ZippyshareCom(SimpleHoster):
- __name__ = "ZippyshareCom"
- __type__ = "hoster"
- __version__ = "0.78"
-
- __pattern__ = r'http://www\d{0,2}\.zippyshare\.com/v(/|iew\.jsp.*key=)(?P<KEY>[\w^_]+)'
- __config__ = [("use_premium", "bool", "Use premium account if available", True)]
-
- __description__ = """Zippyshare.com hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("sebdelsol", "seb.morin@gmail.com")]
-
-
- COOKIES = [("zippyshare.com", "ziplocale", "en")]
-
- NAME_PATTERN = r'(<title>Zippyshare.com - |"/)(?P<N>[^/]+)(</title>|";)'
- SIZE_PATTERN = r'>Size:.+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
- OFFLINE_PATTERN = r'does not exist (anymore )?on this server<'
-
- LINK_PREMIUM_PATTERN = r"document.location = '(.+?)'"
-
-
- def setup(self):
- self.chunkLimit = -1
- self.multiDL = True
- self.resumeDownload = True
-
-
- def handleFree(self, pyfile):
- recaptcha = ReCaptcha(self)
- captcha_key = recaptcha.detect_key()
-
- if captcha_key:
- try:
- self.link = re.search(self.LINK_PREMIUM_PATTERN, self.html)
- recaptcha.challenge()
-
- except Exception, e:
- self.error(e)
-
- else:
- self.link = self.get_link()
-
- if self.link and pyfile.name == 'file.html':
- pyfile.name = urllib.unquote(self.link.split('/')[-1])
-
-
- def get_link(self):
- # get all the scripts inside the html body
- soup = BeautifulSoup(self.html)
- scripts = (s.getText().strip() for s in soup.body.findAll('script', type='text/javascript'))
-
- # meant to be populated with the initialization of all the DOM elements found in the scripts
- initScripts = set()
-
- def replElementById(element):
- id = element.group(1) # id might be either 'x' (a real id) or x (a variable)
- attr = element.group(4) # attr might be None
-
- varName = re.sub(r'-', '', 'GVAR[%s+"_%s"]' %(id, attr))
-
- realid = id.strip('"\'')
- if id != realid: #id is not a variable, so look for realid.attr in the html
- initValues = filter(None, [elt.get(attr, None) for elt in soup.findAll(id=realid)])
- initValue = '"%s"' % initValues[-1] if initValues else 'null'
- initScripts.add('%s = %s;' % (varName, initValue))
-
- return varName
-
- # handle all getElementById
- reVar = r'document.getElementById\(([\'"\w-]+)\)(\.)?(getAttribute\([\'"])?(\w+)?([\'"]\))?'
- scripts = [re.sub(reVar, replElementById, script) for script in scripts if script]
-
- # add try/catch in JS to handle deliberate errors
- scripts = ['\n'.join(('try{', script, '} catch(err){}')) for script in scripts]
-
- # get the file's url by evaluating all the scripts
- scripts = ['var GVAR = {}'] + list(initScripts) + scripts + ['GVAR["dlbutton_href"]']
- return self.js.eval('\n'.join(scripts))
-
-
-getInfo = create_getInfo(ZippyshareCom)
diff --git a/module/plugins/hoster/__init__.py b/module/plugins/hoster/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/hoster/__init__.py
+++ /dev/null
diff --git a/module/plugins/internal/CaptchaService.py b/module/plugins/internal/CaptchaService.py
deleted file mode 100644
index b6afad22a..000000000
--- a/module/plugins/internal/CaptchaService.py
+++ /dev/null
@@ -1,517 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-import time
-import urlparse
-
-from base64 import b64encode
-
-from module.common.json_layer import json_loads
-from module.plugins.Plugin import Base
-
-
-#@TODO: Extend (new) Plugin class; remove all `html` args
-class CaptchaService(Base):
- __name__ = "CaptchaService"
- __type__ = "captcha"
- __version__ = "0.26"
-
- __description__ = """Base captcha service plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- key = None #: last key detected
-
-
- def __init__(self, plugin):
- self.plugin = plugin
- super(CaptchaService, self).__init__(plugin.core)
-
-
- def detect_key(self, html=None):
- raise NotImplementedError
-
-
- def challenge(self, key=None, html=None):
- raise NotImplementedError
-
-
- def result(self, server, challenge):
- raise NotImplementedError
-
-
-class ReCaptcha(CaptchaService):
- __name__ = "ReCaptcha"
- __type__ = "captcha"
- __version__ = "0.15"
-
- __description__ = """ReCaptcha captcha service plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org"),
- ("Walter Purcaro", "vuolter@gmail.com"),
- ("zapp-brannigan", "fuerst.reinje@web.de")]
-
-
- KEY_V2_PATTERN = r'(?:data-sitekey=["\']|["\']sitekey["\']:\s*["\'])([\w-]+)'
- KEY_V1_PATTERN = r'(?:recaptcha(?:/api|\.net)/(?:challenge|noscript)\?k=|Recaptcha\.create\s*\(\s*["\'])([\w-]+)'
-
-
- def detect_key(self, html=None):
- if not html:
- if hasattr(self.plugin, "html") and self.plugin.html:
- html = self.plugin.html
- else:
- errmsg = _("ReCaptcha html not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- m = re.search(self.KEY_V2_PATTERN, html) or re.search(self.KEY_V1_PATTERN, html)
- if m:
- self.key = m.group(1).strip()
- self.logDebug("Key: %s" % self.key)
- return self.key
- else:
- self.logDebug("Key not found")
- return None
-
-
- def challenge(self, key=None, html=None, version=None):
- if not key:
- if self.detect_key(html):
- key = self.key
- else:
- errmsg = _("ReCaptcha key not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- if version in (1, 2):
- return getattr(self, "_challenge_v%s" % version)(key)
-
- elif not html and hasattr(self.plugin, "html") and self.plugin.html:
- version = 2 if re.search(self.KEY_V2_PATTERN, self.plugin.html) else 1
- return self.challenge(key, self.plugin.html, version)
-
- else:
- errmsg = _("ReCaptcha html not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
-
- def _challenge_v1(self, key):
- html = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge",
- get={'k': key})
- try:
- challenge = re.search("challenge : '(.+?)',", html).group(1)
- server = re.search("server : '(.+?)',", html).group(1)
-
- except AttributeError:
- errmsg = _("ReCaptcha challenge pattern not found")
- self.plugin.fail(errmsg)
- raise AttributeError(errmsg)
-
- self.logDebug("Challenge: %s" % challenge)
-
- return self.result(server, challenge), challenge
-
-
- def result(self, server, challenge):
- result = self.plugin.decryptCaptcha("%simage" % server,
- get={'c': challenge},
- cookies=True,
- forceUser=True,
- imgtype="jpg")
-
- self.logDebug("Result: %s" % result)
-
- return result
-
-
- def _collectApiInfo(self):
- html = self.plugin.req.load("http://www.google.com/recaptcha/api.js")
- a = re.search(r'po.src = \'(.*?)\';', html).group(1)
- vers = a.split("/")[5]
-
- self.logDebug("API version: %s" %vers)
-
- language = a.split("__")[1].split(".")[0]
-
- self.logDebug("API language: %s" % language)
-
- html = self.plugin.req.load("https://apis.google.com/js/api.js")
- b = re.search(r'"h":"(.*?)","', html).group(1)
- jsh = b.decode('unicode-escape')
-
- self.logDebug("API jsh-string: %s" % jsh)
-
- return vers, language, jsh
-
-
- def _prepareTimeAndRpc(self):
- self.plugin.req.load("http://www.google.com/recaptcha/api2/demo")
-
- millis = int(round(time.time() * 1000))
-
- self.logDebug("Time: %s" % millis)
-
- rand = random.randint(1, 99999999)
- a = "0.%s" % str(rand * 2147483647)
- rpc = int(100000000 * float(a))
-
- self.logDebug("Rpc-token: %s" % rpc)
-
- return millis, rpc
-
-
- def _challenge_v2(self, key, parent=None):
- if parent is None:
- try:
- parent = urlparse.urljoin("http://", urlparse.urlparse(self.plugin.pyfile.url).netloc)
-
- except Exception:
- parent = ""
-
- botguardstring = "!A"
- vers, language, jsh = self._collectApiInfo()
- millis, rpc = self._prepareTimeAndRpc()
-
- html = self.plugin.req.load("https://www.google.com/recaptcha/api2/anchor",
- get={'k' : key,
- 'hl' : language,
- 'v' : vers,
- 'usegapi' : "1",
- 'jsh' : "%s#id=IO_%s" % (jsh, millis),
- 'parent' : parent,
- 'pfname' : "",
- 'rpctoken': rpc})
-
- token1 = re.search(r'id="recaptcha-token" value="(.*?)">', html)
- self.logDebug("Token #1: %s" % token1.group(1))
-
- html = self.plugin.req.load("https://www.google.com/recaptcha/api2/frame",
- get={'c' : token1.group(1),
- 'hl' : language,
- 'v' : vers,
- 'bg' : botguardstring,
- 'k' : key,
- 'usegapi': "1",
- 'jsh' : jsh}).decode('unicode-escape')
-
- token2 = re.search(r'"finput","(.*?)",', html)
- self.logDebug("Token #2: %s" % token2.group(1))
-
- token3 = re.search(r'"rresp","(.*?)",', html)
- self.logDebug("Token #3: %s" % token3.group(1))
-
- millis_captcha_loading = int(round(time.time() * 1000))
- captcha_response = self.plugin.decryptCaptcha("https://www.google.com/recaptcha/api2/payload",
- get={'c':token3.group(1), 'k':key},
- cookies=True,
- forceUser=True)
- response = b64encode('{"response":"%s"}' % captcha_response)
-
- self.logDebug("Result: %s" % response)
-
- timeToSolve = int(round(time.time() * 1000)) - millis_captcha_loading
- timeToSolveMore = timeToSolve + int(float("0." + str(random.randint(1, 99999999))) * 500)
-
- html = self.plugin.req.load("https://www.google.com/recaptcha/api2/userverify",
- post={'k' : key,
- 'c' : token3.group(1),
- 'response': response,
- 't' : timeToSolve,
- 'ct' : timeToSolveMore,
- 'bg' : botguardstring})
-
- token4 = re.search(r'"uvresp","(.*?)",', html)
- self.logDebug("Token #4: %s" % token4.group(1))
-
- result = token4.group(1)
-
- return result, None
-
-
-class AdsCaptcha(CaptchaService):
- __name__ = "AdsCaptcha"
- __type__ = "captcha"
- __version__ = "0.08"
-
- __description__ = """AdsCaptcha captcha service plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- CAPTCHAID_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?CaptchaId=(\d+)'
- PUBLICKEY_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?PublicKey=([\w-]+)'
-
-
- def detect_key(self, html=None):
- if not html:
- if hasattr(self.plugin, "html") and self.plugin.html:
- html = self.plugin.html
- else:
- errmsg = _("AdsCaptcha html not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- m = re.search(self.PUBLICKEY_PATTERN, html)
- n = re.search(self.CAPTCHAID_PATTERN, html)
- if m and n:
- self.key = (m.group(1).strip(), n.group(1).strip()) #: key is the tuple(PublicKey, CaptchaId)
- self.logDebug("Key|id: %s | %s" % self.key)
- return self.key
- else:
- self.logDebug("Key or id not found")
- return None
-
-
- def challenge(self, key=None, html=None):
- if not key:
- if self.detect_key(html):
- key = self.key
- else:
- errmsg = _("AdsCaptcha key not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- PublicKey, CaptchaId = key
-
- html = self.plugin.req.load("http://api.adscaptcha.com/Get.aspx",
- get={'CaptchaId': CaptchaId,
- 'PublicKey': PublicKey})
- try:
- challenge = re.search("challenge: '(.+?)',", html).group(1)
- server = re.search("server: '(.+?)',", html).group(1)
-
- except AttributeError:
- errmsg = _("AdsCaptcha challenge pattern not found")
- self.plugin.fail(errmsg)
- raise AttributeError(errmsg)
-
- self.logDebug("Challenge: %s" % challenge)
-
- return self.result(server, challenge), challenge
-
-
- def result(self, server, challenge):
- result = self.plugin.decryptCaptcha("%sChallenge.aspx" % server,
- get={'cid': challenge, 'dummy': random.random()},
- cookies=True,
- imgtype="jpg")
-
- self.logDebug("Result: %s" % result)
-
- return result
-
-
-class SolveMedia(CaptchaService):
- __name__ = "SolveMedia"
- __type__ = "captcha"
- __version__ = "0.12"
-
- __description__ = """SolveMedia captcha service plugin"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team", "admin@pyload.org")]
-
-
- KEY_PATTERN = r'api\.solvemedia\.com/papi/challenge\.(?:no)?script\?k=(.+?)["\']'
-
-
- def detect_key(self, html=None):
- if not html:
- if hasattr(self.plugin, "html") and self.plugin.html:
- html = self.plugin.html
- else:
- errmsg = _("SolveMedia html not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- m = re.search(self.KEY_PATTERN, html)
- if m:
- self.key = m.group(1).strip()
- self.logDebug("Key: %s" % self.key)
- return self.key
- else:
- self.logDebug("Key not found")
- return None
-
-
- def challenge(self, key=None, html=None):
- if not key:
- if self.detect_key(html):
- key = self.key
- else:
- errmsg = _("SolveMedia key not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- html = self.plugin.req.load("http://api.solvemedia.com/papi/challenge.noscript",
- get={'k': key})
- try:
- challenge = re.search(r'<input type=hidden name="adcopy_challenge" id="adcopy_challenge" value="(.+?)">',
- html).group(1)
- server = "http://api.solvemedia.com/papi/media"
-
- except AttributeError:
- errmsg = _("SolveMedia challenge pattern not found")
- self.plugin.fail(errmsg)
- raise AttributeError(errmsg)
-
- self.logDebug("Challenge: %s" % challenge)
-
- result = self.result(server, challenge)
-
- try:
- magic = re.search(r'name="magic" value="(.+?)"', html).group(1)
-
- except AttributeError:
- self.logDebug("Magic code not found")
-
- else:
- if not self._verify(key, magic, result, challenge):
- self.logDebug("Captcha code was invalid")
-
- return result, challenge
-
-
- def _verify(self, key, magic, result, challenge, ref=None): #@TODO: Clean up
- if ref is None:
- try:
- ref = self.plugin.pyfile.url
-
- except Exception:
- ref = ""
-
- html = self.plugin.req.load("http://api.solvemedia.com/papi/verify.noscript",
- post={'adcopy_response' : result,
- 'k' : key,
- 'l' : "en",
- 't' : "img",
- 's' : "standard",
- 'magic' : magic,
- 'adcopy_challenge' : challenge,
- 'ref' : ref})
- try:
- html = self.plugin.req.load(re.search(r'URL=(.+?)">', html).group(1))
- gibberish = re.search(r'id=gibberish>(.+?)</textarea>', html).group(1)
-
- except Exception:
- return False
-
- else:
- return True
-
-
- def result(self, server, challenge):
- result = self.plugin.decryptCaptcha(server,
- get={'c': challenge},
- cookies=True,
- imgtype="gif")
-
- self.logDebug("Result: %s" % result)
-
- return result
-
-
-class AdYouLike(CaptchaService):
- __name__ = "AdYouLike"
- __type__ = "captcha"
- __version__ = "0.05"
-
- __description__ = """AdYouLike captcha service plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- AYL_PATTERN = r'Adyoulike\.create\s*\((.+?)\)'
- CALLBACK_PATTERN = r'(Adyoulike\.g\._jsonp_\d+)'
-
-
- def detect_key(self, html=None):
- if not html:
- if hasattr(self.plugin, "html") and self.plugin.html:
- html = self.plugin.html
- else:
- errmsg = _("AdYouLike html not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- m = re.search(self.AYL_PATTERN, html)
- n = re.search(self.CALLBACK_PATTERN, html)
- if m and n:
- self.key = (m.group(1).strip(), n.group(1).strip())
- self.logDebug("Ayl|callback: %s | %s" % self.key)
- return self.key #: key is the tuple(ayl, callback)
- else:
- self.logDebug("Ayl or callback not found")
- return None
-
-
- def challenge(self, key=None, html=None):
- if not key:
- if self.detect_key(html):
- key = self.key
- else:
- errmsg = _("AdYouLike key not found")
- self.plugin.fail(errmsg)
- raise TypeError(errmsg)
-
- ayl, callback = key
-
- # {"adyoulike":{"key":"P~zQ~O0zV0WTiAzC-iw0navWQpCLoYEP"},
- # "all":{"element_id":"ayl_private_cap_92300","lang":"fr","env":"prod"}}
- ayl = json_loads(ayl)
-
- html = self.plugin.req.load("http://api-ayl.appspot.com/challenge",
- get={'key' : ayl['adyoulike']['key'],
- 'env' : ayl['all']['env'],
- 'callback': callback})
- try:
- challenge = json_loads(re.search(callback + r'\s*\((.+?)\)', html).group(1))
-
- except AttributeError:
- errmsg = _("AdYouLike challenge pattern not found")
- self.plugin.fail(errmsg)
- raise AttributeError(errmsg)
-
- self.logDebug("Challenge: %s" % challenge)
-
- return self.result(ayl, challenge), challenge
-
-
- def result(self, server, 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"})
-
- if isinstance(server, basestring):
- server = json_loads(server)
-
- if isinstance(challenge, basestring):
- challenge = json_loads(challenge)
-
- try:
- instructions_visual = challenge['translations'][server['all']['lang']]['instructions_visual']
- result = re.search(u'«(.+?)»', instructions_visual).group(1).strip()
-
- except AttributeError:
- errmsg = _("AdYouLike result not found")
- self.plugin.fail(errmsg)
- raise AttributeError(errmsg)
-
- result = {'_ayl_captcha_engine' : "adyoulike",
- '_ayl_env' : server['all']['env'],
- '_ayl_tid' : challenge['tid'],
- '_ayl_token_challenge': challenge['token'],
- '_ayl_response' : response}
-
- self.logDebug("Result: %s" % result)
-
- return result
diff --git a/module/plugins/internal/DeadCrypter.py b/module/plugins/internal/DeadCrypter.py
deleted file mode 100644
index 866d177cf..000000000
--- a/module/plugins/internal/DeadCrypter.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import create_getInfo
-from module.plugins.Crypter import Crypter as _Crypter
-
-
-class DeadCrypter(_Crypter):
- __name__ = "DeadCrypter"
- __type__ = "crypter"
- __version__ = "0.04"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """ Crypter is no longer available """
- __license__ = "GPLv3"
- __authors__ = [("stickell", "l.stickell@yahoo.it")]
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- api = super(DeadCrypter, self).apiInfo(url, get, post)
- api['status'] = 1
- return api
-
-
- def setup(self):
- self.pyfile.error = "Crypter is no longer available"
- self.offline() #@TODO: self.offline("Crypter is no longer available")
-
-
-getInfo = create_getInfo(DeadCrypter)
diff --git a/module/plugins/internal/DeadHoster.py b/module/plugins/internal/DeadHoster.py
deleted file mode 100644
index a6ad92607..000000000
--- a/module/plugins/internal/DeadHoster.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleHoster import create_getInfo
-from module.plugins.Hoster import Hoster as _Hoster
-
-
-class DeadHoster(_Hoster):
- __name__ = "DeadHoster"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """ Hoster is no longer available """
- __license__ = "GPLv3"
- __authors__ = [("zoidberg", "zoidberg@mujmail.cz")]
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- api = super(DeadHoster, self).apiInfo(url, get, post)
- api['status'] = 1
- return api
-
-
- def setup(self):
- self.pyfile.error = "Hoster is no longer available"
- self.offline() #@TODO: self.offline("Hoster is no longer available")
-
-
-getInfo = create_getInfo(DeadHoster)
diff --git a/module/plugins/internal/Extractor.py b/module/plugins/internal/Extractor.py
deleted file mode 100644
index 159b65ffe..000000000
--- a/module/plugins/internal/Extractor.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-
-from module.PyFile import PyFile
-
-
-class ArchiveError(Exception):
- pass
-
-
-class CRCError(Exception):
- pass
-
-
-class PasswordError(Exception):
- pass
-
-
-class Extractor:
- __name__ = "Extractor"
- __version__ = "0.24"
-
- __description__ = """Base extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com"),
- ("Immenz" , "immenz@gmx.net" )]
-
-
- EXTENSIONS = []
- VERSION = ""
- REPAIR = False
-
-
- @classmethod
- def isArchive(cls, filename):
- name = os.path.basename(filename).lower()
- return any(name.endswith(ext) for ext in cls.EXTENSIONS)
-
-
- @classmethod
- def isMultipart(cls, filename):
- return False
-
-
- @classmethod
- def isUsable(cls):
- """ Check if system statisfy dependencies
- :return: boolean
- """
- return None
-
-
- @classmethod
- def getTargets(cls, files_ids):
- """ Filter suited targets from list of filename id tuple list
- :param files_ids: List of filepathes
- :return: List of targets, id tuple list
- """
- targets = []
- processed = []
-
- for fname, id, fout in files_ids:
- if cls.isArchive(fname):
- pname = re.sub(cls.re_multipart, '', fname) if cls.isMultipart(fname) else os.path.splitext(fname)[0]
- if pname not in processed:
- processed.append(pname)
- targets.append((fname, id, fout))
- return targets
-
-
- def __init__(self, manager, filename, out,
- fullpath=True,
- overwrite=False,
- excludefiles=[],
- renice=0,
- delete='No',
- keepbroken=False,
- fid=None):
- """ Initialize extractor for specific file """
- self.manager = manager
- self.filename = filename
- self.out = out
- self.fullpath = fullpath
- self.overwrite = overwrite
- self.excludefiles = excludefiles
- self.renice = renice
- self.delete = delete
- self.keepbroken = keepbroken
- self.files = [] #: Store extracted files here
-
- pyfile = self.manager.core.files.getFile(fid) if fid else None
- self.notifyProgress = lambda x: pyfile.setProgress(x) if pyfile else lambda x: None
-
-
- def init(self):
- """ Initialize additional data structures """
- pass
-
-
- def check(self):
- """Quick Check by listing content of archive.
- Raises error if password is needed, integrity is questionable or else.
-
- :raises PasswordError
- :raises CRCError
- :raises ArchiveError
- """
- raise NotImplementedError
-
- def verify(self):
- """Testing with Extractors buildt-in method
- Raises error if password is needed, integrity is questionable or else.
-
- :raises PasswordError
- :raises CRCError
- :raises ArchiveError
- """
- raise NotImplementedError
-
-
- def repair(self):
- return None
-
-
- def extract(self, password=None):
- """Extract the archive. Raise specific errors in case of failure.
-
- :param progress: Progress function, call this to update status
- :param password password to use
- :raises PasswordError
- :raises CRCError
- :raises ArchiveError
- :return:
- """
- raise NotImplementedError
-
-
- def getDeleteFiles(self):
- """Return list of files to delete, do *not* delete them here.
-
- :return: List with paths of files to delete
- """
- return [self.filename]
-
-
- def list(self, password=None):
- """Populate self.files at some point while extracting"""
- return self.files
diff --git a/module/plugins/internal/MultiHook.py b/module/plugins/internal/MultiHook.py
deleted file mode 100644
index 01ff4b07d..000000000
--- a/module/plugins/internal/MultiHook.py
+++ /dev/null
@@ -1,288 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import traceback
-
-from module.plugins.Hook import Hook
-from module.utils import decode, remove_chars
-
-
-class MultiHook(Hook):
- __name__ = "MultiHook"
- __type__ = "hook"
- __version__ = "0.45"
-
- __config__ = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
- ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
- ("reload" , "bool" , "Reload plugin list" , True ),
- ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
-
- __description__ = """Hook plugin for multi hoster/crypter"""
- __license__ = "GPLv3"
- __authors__ = [("pyLoad Team" , "admin@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- MIN_RELOAD_INTERVAL = 1 * 60 * 60 #: 1 hour
-
- DOMAIN_REPLACEMENTS = [(r'180upload\.com' , "hundredeightyupload.com"),
- (r'bayfiles\.net' , "bayfiles.com" ),
- (r'cloudnator\.com' , "shragle.com" ),
- (r'dfiles\.eu' , "depositfiles.com" ),
- (r'easy-share\.com' , "crocko.com" ),
- (r'freakshare\.net' , "freakshare.com" ),
- (r'hellshare\.com' , "hellshare.cz" ),
- (r'ifile\.it' , "filecloud.io" ),
- (r'nowdownload\.\w+', "nowdownload.sx" ),
- (r'nowvideo\.\w+' , "nowvideo.sx" ),
- (r'putlocker\.com' , "firedrive.com" ),
- (r'share-?rapid\.cz', "multishare.cz" ),
- (r'ul\.to' , "uploaded.to" ),
- (r'uploaded\.net' , "uploaded.to" ),
- (r'uploadhero\.co' , "uploadhero.com" ),
- (r'zshares\.net' , "zshare.net" ),
- (r'^1' , "one" ),
- (r'^2' , "two" ),
- (r'^3' , "three" ),
- (r'^4' , "four" ),
- (r'^5' , "five" ),
- (r'^6' , "six" ),
- (r'^7' , "seven" ),
- (r'^8' , "eight" ),
- (r'^9' , "nine" ),
- (r'^0' , "zero" )]
-
-
- def setup(self):
- self.info = {} #@TODO: Remove in 0.4.10
-
- self.plugins = []
- self.supported = []
- self.new_supported = []
-
- self.account = None
- self.pluginclass = None
- self.pluginmodule = None
- self.pluginname = None
- self.plugintype = None
-
- self.initPlugin()
-
-
- def initPlugin(self):
- self.pluginname = self.__name__.rsplit("Hook", 1)[0]
- plugin, self.plugintype = self.core.pluginManager.findPlugin(self.pluginname)
-
- if plugin:
- self.pluginmodule = self.core.pluginManager.loadModule(self.plugintype, self.pluginname)
- self.pluginclass = getattr(self.pluginmodule, self.pluginname)
- else:
- self.logWarning("Hook plugin will be deactivated due missing plugin reference")
- self.setConfig('activated', False)
-
-
- def loadAccount(self):
- self.account = self.core.accountManager.getAccountPlugin(self.pluginname)
-
- if self.account and not self.account.canUse():
- self.account = None
-
- if not self.account and hasattr(self.pluginclass, "LOGIN_ACCOUNT") and self.pluginclass.LOGIN_ACCOUNT:
- self.logWarning("Hook plugin will be deactivated due missing account reference")
- self.setConfig('activated', False)
-
-
- def getURL(self, *args, **kwargs): #@TODO: Remove in 0.4.10
- """ see HTTPRequest for argument list """
- h = pyreq.getHTTPRequest(timeout=120)
- try:
- if not 'decode' in kwargs:
- kwargs['decode'] = True
- rep = h.load(*args, **kwargs)
- finally:
- h.close()
-
- return rep
-
-
- def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
- """getConfig with default value - sublass may not implements all config options"""
- try:
- return self.getConf(option)
-
- except KeyError:
- return default
-
-
- def pluginsCached(self):
- if self.plugins:
- return self.plugins
-
- for _i in xrange(2):
- try:
- pluginset = self._pluginSet(self.getHosters())
- break
-
- except Exception, e:
- self.logDebug(e, "Waiting 1 minute and retry")
- time.sleep(60)
- else:
- self.logWarning(_("Fallback to default reload interval due plugin parse error"))
- self.interval = self.MIN_RELOAD_INTERVAL
- return list()
-
- try:
- configmode = self.getConfig("pluginmode", 'all')
- if configmode in ("listed", "unlisted"):
- pluginlist = self.getConfig("pluginlist", '').replace('|', ',').replace(';', ',').split(',')
- configset = self._pluginSet(pluginlist)
-
- if configmode == "listed":
- pluginset &= configset
- else:
- pluginset -= configset
-
- except Exception, e:
- self.logError(e)
-
- self.plugins = list(pluginset)
-
- return self.plugins
-
-
- def _pluginSet(self, plugins):
- regexp = re.compile(r'^[\w\-.^_]{3,63}\.[a-zA-Z]{2,}$', re.U)
- plugins = [decode(p.strip()).lower() for p in plugins if regexp.match(p.strip())]
-
- for r in self.DOMAIN_REPLACEMENTS:
- rf, rt = r
- repr = re.compile(rf, re.I|re.U)
- plugins = [re.sub(rf, rt, p) if repr.match(p) else p for p in plugins]
-
- return set(plugins)
-
-
- def getHosters(self):
- """Load list of supported hoster
-
- :return: List of domain names
- """
- raise NotImplementedError
-
-
- #: Threaded _periodical, remove in 0.4.10 and use built-in flag for that
- 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:
- traceback.print_exc()
-
- self.cb = self.core.scheduler.addJob(self.interval, self._periodical)
-
-
- def periodical(self):
- """reload plugin list periodically"""
- self.loadAccount()
-
- if self.getConfig("reload", True):
- self.interval = max(self.getConfig("reloadinterval", 12) * 60 * 60, self.MIN_RELOAD_INTERVAL)
- else:
- self.core.scheduler.removeJob(self.cb)
- self.cb = None
-
- self.logInfo(_("Reloading supported %s list") % self.plugintype)
-
- old_supported = self.supported
-
- self.supported = []
- self.new_supported = []
- self.plugins = []
-
- self.overridePlugins()
-
- old_supported = [plugin for plugin in old_supported if plugin not in self.supported]
-
- if old_supported:
- self.logDebug("Unload: %s" % ", ".join(old_supported))
- for plugin in old_supported:
- self.unloadPlugin(plugin)
-
-
- def overridePlugins(self):
- excludedList = []
-
- if self.plugintype == "hoster":
- pluginMap = dict((name.lower(), name) for name in self.core.pluginManager.hosterPlugins.iterkeys())
- accountList = [account.type.lower() for account in self.core.api.getAccounts(False) if account.valid and account.premium]
- else:
- pluginMap = {}
- accountList = [name[::-1].replace("Folder"[::-1], "", 1).lower()[::-1] for name in self.core.pluginManager.crypterPlugins.iterkeys()]
-
- for plugin in self.pluginsCached():
- name = remove_chars(plugin, "-.")
-
- if name in accountList:
- excludedList.append(plugin)
- else:
- if name in pluginMap:
- self.supported.append(pluginMap[name])
- else:
- self.new_supported.append(plugin)
-
- if not self.supported and not self.new_supported:
- self.logError(_("No %s loaded") % self.plugintype)
- return
-
- # inject plugin plugin
- self.logDebug("Overwritten %ss: %s" % (self.plugintype, ", ".join(sorted(self.supported))))
-
- for plugin in self.supported:
- hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
- hdict['new_module'] = self.pluginmodule
- hdict['new_name'] = self.pluginname
-
- if excludedList:
- self.logInfo(_("%ss not overwritten: %s") % (self.plugintype.capitalize(), ", ".join(sorted(excludedList))))
-
- if self.new_supported:
- plugins = sorted(self.new_supported)
-
- self.logDebug("New %ss: %s" % (self.plugintype, ", ".join(plugins)))
-
- # create new regexp
- regexp = r'.*(?P<DOMAIN>%s).*' % "|".join(x.replace('.', '\.') for x in plugins)
- if hasattr(self.pluginclass, "__pattern__") and isinstance(self.pluginclass.__pattern__, basestring) and '://' in self.pluginclass.__pattern__:
- regexp = r'%s|%s' % (self.pluginclass.__pattern__, regexp)
-
- self.logDebug("Regexp: %s" % regexp)
-
- hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
- hdict['pattern'] = regexp
- hdict['re'] = re.compile(regexp)
-
-
- def unloadPlugin(self, plugin):
- hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
- if "module" in hdict:
- hdict.pop('module', None)
-
- if "new_module" in hdict:
- hdict.pop('new_module', None)
- hdict.pop('new_name', None)
-
-
- def unload(self):
- """Remove override for all plugins. Scheduler job is removed by hookmanager"""
- for plugin in self.supported:
- self.unloadPlugin(plugin)
-
- # reset pattern
- hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
-
- hdict['pattern'] = getattr(self.pluginclass, "__pattern__", r'^unmatchable$')
- hdict['re'] = re.compile(hdict['pattern'])
diff --git a/module/plugins/internal/MultiHoster.py b/module/plugins/internal/MultiHoster.py
deleted file mode 100644
index 350397f8b..000000000
--- a/module/plugins/internal/MultiHoster.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Plugin import Fail, Retry
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns, set_cookies
-
-
-class MultiHoster(SimpleHoster):
- __name__ = "MultiHoster"
- __type__ = "hoster"
- __version__ = "0.39"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium" , "bool", "Use premium account if available" , True),
- ("revertfailed", "bool", "Revert to standard download if fails", True)]
-
- __description__ = """Multi hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- LOGIN_ACCOUNT = True
-
-
- def setup(self):
- self.chunkLimit = 1
- self.multiDL = bool(self.account)
- self.resumeDownload = self.premium
-
-
- def prepare(self):
- self.info = {}
- self.html = ""
- self.link = "" #@TODO: Move to hoster class in 0.4.10
- self.directDL = False #@TODO: Move to hoster class in 0.4.10
-
- if not self.getConfig('use_premium', True):
- self.retryFree()
-
- if self.LOGIN_ACCOUNT and not self.account:
- self.fail(_("Required account not found"))
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
- if self.DIRECT_LINK is None:
- self.directDL = self.__pattern__ != r'^unmatchable$' and re.match(self.__pattern__, self.pyfile.url)
- else:
- self.directDL = self.DIRECT_LINK
-
- self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
-
-
- def process(self, pyfile):
- try:
- self.prepare()
-
- if self.directDL:
- self.checkInfo()
- self.logDebug("Looking for direct download link...")
- self.handleDirect(pyfile)
-
- if not self.link and not self.lastDownload:
- self.preload()
-
- self.checkErrors()
- self.checkStatus(getinfo=False)
-
- if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as premium download")
- self.handlePremium(pyfile)
-
- elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as free download")
- self.handleFree(pyfile)
-
- self.downloadLink(self.link, True)
- self.checkFile()
-
- except Fail, e: #@TODO: Move to PluginThread in 0.4.10
- if self.premium:
- self.logWarning(_("Premium download failed"))
- self.retryFree()
-
- elif self.getConfig("revertfailed", True) \
- and "new_module" in self.core.pluginManager.hosterPlugins[self.__name__]:
- hdict = self.core.pluginManager.hosterPlugins[self.__name__]
-
- tmp_module = hdict['new_module']
- tmp_name = hdict['new_name']
- hdict.pop('new_module', None)
- hdict.pop('new_name', None)
-
- pyfile.initPlugin()
-
- hdict['new_module'] = tmp_module
- hdict['new_name'] = tmp_name
-
- raise Retry(_("Revert to original hoster plugin"))
-
- else:
- raise Fail(e)
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
- def handleFree(self, pyfile):
- if self.premium:
- raise NotImplementedError
- else:
- self.fail(_("Required premium account not found"))
diff --git a/module/plugins/internal/SevenZip.py b/module/plugins/internal/SevenZip.py
deleted file mode 100644
index 624f6c939..000000000
--- a/module/plugins/internal/SevenZip.py
+++ /dev/null
@@ -1,153 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import subprocess
-
-from module.plugins.internal.UnRar import ArchiveError, CRCError, PasswordError, UnRar, renice
-from module.utils import fs_encode, save_join
-
-
-class SevenZip(UnRar):
- __name__ = "SevenZip"
- __version__ = "0.11"
-
- __description__ = """7-Zip extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Michael Nowak" , "" ),
- ("Walter Purcaro", "vuolter@gmail.com")]
-
-
- CMD = "7z"
- VERSION = ""
-
- EXTENSIONS = [".7z", ".xz", ".zip", ".gz", ".gzip", ".tgz", ".bz2", ".bzip2",
- ".tbz2", ".tbz", ".tar", ".wim", ".swm", ".lzma", ".rar", ".cab",
- ".arj", ".z", ".taz", ".cpio", ".rpm", ".deb", ".lzh", ".lha",
- ".chm", ".chw", ".hxs", ".iso", ".msi", ".doc", ".xls", ".ppt",
- ".dmg", ".xar", ".hfs", ".exe", ".ntfs", ".fat", ".vhd", ".mbr",
- ".squashfs", ".cramfs", ".scap"]
-
-
- #@NOTE: there are some more uncovered 7z formats
- re_filelist = re.compile(r'([\d\:]+)\s+([\d\:]+)\s+([\w\.]+)\s+(\d+)\s+(\d+)\s+(.+)')
- re_wrongpwd = re.compile(r'(Can not open encrypted archive|Wrong password|Encrypted\s+\=\s+\+)', re.I)
- re_wrongcrc = re.compile(r'CRC Failed|Can not open file', re.I)
- re_version = re.compile(r'7-Zip\s(?:\[64\]\s)?(\d+\.\d+)', re.I)
-
-
- @classmethod
- def isUsable(cls):
- if os.name == "nt":
- cls.CMD = os.path.join(pypath, "7z.exe")
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- else:
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
-
- m = cls.re_version.search(out)
- cls.VERSION = m.group(1) if m else '(version unknown)'
-
- return True
-
-
- def verify(self, password):
- # 7z can't distinguish crc and pw error in test
- p = self.call_cmd("l", "-slt", fs_encode(self.filename))
- out, err = p.communicate()
-
- if self.re_wrongpwd.search(out):
- raise PasswordError
-
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- if self.re_wrongcrc.search(err):
- raise CRCError(err)
-
-
-
- def check(self, password):
- p = self.call_cmd("l", "-slt", fs_encode(self.filename))
- out, err = p.communicate()
-
- # check if output or error macthes the 'wrong password'-Regexp
- if self.re_wrongpwd.search(out):
- raise PasswordError
-
- if self.re_wrongcrc.search(out):
- raise CRCError(_("Header protected"))
-
-
- def repair(self):
- return False
-
-
- def extract(self, password=None):
- command = "x" if self.fullpath else "e"
-
- p = self.call_cmd(command, '-o' + self.out, fs_encode(self.filename), password=password)
-
- renice(p.pid, self.renice)
-
- # communicate and retrieve stderr
- self._progress(p)
- err = p.stderr.read().strip()
-
- if err:
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- elif self.re_wrongcrc.search(err):
- raise CRCError(err)
-
- else: #: raise error if anything is on stderr
- raise ArchiveError(err)
-
- if p.returncode > 1:
- raise ArchiveError(_("Process return code: %d") % p.returncode)
-
- self.files = self.list(password)
-
-
- def list(self, password=None):
- command = "l" if self.fullpath else "l"
-
- p = self.call_cmd(command, fs_encode(self.filename), password=password)
- out, err = p.communicate()
-
- if "Can not open" in err:
- raise ArchiveError(_("Cannot open file"))
-
- if p.returncode > 1:
- raise ArchiveError(_("Process return code: %d") % p.returncode)
-
- result = set()
- for groups in self.re_filelist.findall(out):
- f = groups[-1].strip()
- result.add(save_join(self.out, f))
-
- return list(result)
-
-
- def call_cmd(self, command, *xargs, **kwargs):
- args = []
-
- #overwrite flag
- if self.overwrite:
- 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.manager.logDebug(" ".join(call))
-
- p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return p
diff --git a/module/plugins/internal/SimpleCrypter.py b/module/plugins/internal/SimpleCrypter.py
deleted file mode 100644
index 09805cf1a..000000000
--- a/module/plugins/internal/SimpleCrypter.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urlparse
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns, set_cookies
-from module.utils import fixup
-
-
-class SimpleCrypter(Crypter, SimpleHoster):
- __name__ = "SimpleCrypter"
- __type__ = "crypter"
- __version__ = "0.43"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True), #: Overrides core.config['general']['folder_per_package']
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Simple decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- """
- Following patterns should be defined by each crypter:
-
- LINK_PATTERN: Download link or regex to catch links in group(1)
- example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
-
- NAME_PATTERN: (optional) folder name or page title
- example: NAME_PATTERN = r'<title>Files of: (?P<N>[^<]+) folder</title>'
-
- OFFLINE_PATTERN: (optional) Checks if the page is unreachable
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
- example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
-
-
- You can override the getLinks method if you need a more sophisticated way to extract the links.
-
-
- If the links are splitted on multiple pages you can define the PAGES_PATTERN regex:
-
- PAGES_PATTERN: (optional) group(1) should be the number of overall pages containing the links
- example: PAGES_PATTERN = r'Pages: (\d+)'
-
- and its loadPage method:
-
-
- def loadPage(self, page_n):
- return the html of the page number page_n
- """
-
- LINK_PATTERN = None
-
- NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
- URL_REPLACEMENTS = []
-
- TEXT_ENCODING = False #: Set to True or encoding name if encoding in http header is not correct
- COOKIES = True #: or False or list of tuples [(domain, name, value)]
-
- LOGIN_ACCOUNT = False
- LOGIN_PREMIUM = False
-
-
- #@TODO: Remove in 0.4.10
- def init(self):
- account_name = (self.__name__ + ".py").replace("Folder.py", "").replace(".py", "")
- account = self.pyfile.m.core.accountManager.getAccountPlugin(account_name)
-
- if account and account.canUse():
- self.user, data = account.selectAccount()
- self.req = account.getAccountRequest(self.user)
- self.premium = account.isPremium(self.user)
-
- self.account = account
-
-
- def prepare(self):
- self.pyfile.error = "" #@TODO: Remove in 0.4.10
-
- self.info = {}
- self.html = ""
- self.links = [] #@TODO: Move to hoster class in 0.4.10
-
- if self.LOGIN_PREMIUM and not self.premium:
- self.fail(_("Required premium account not found"))
-
- if self.LOGIN_ACCOUNT and not self.account:
- self.fail(_("Required account not found"))
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
- self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
-
-
- def decrypt(self, pyfile):
- self.prepare()
-
- self.preload()
- self.checkInfo()
-
- self.links = self.getLinks()
-
- if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'):
- self.handlePages(pyfile)
-
- self.logDebug("Package has %d links" % len(self.links))
-
- if self.links:
- self.packages = [(self.info['name'], self.links, self.info['folder'])]
-
- elif not self.urls and not self.packages: #@TODO: Remove in 0.4.10
- self.fail(_("No link grabbed"))
-
-
- def checkNameSize(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("File info (BEFORE): %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("File info (AFTER): %s" % self.info)
-
- try:
- url = self.info['url'].strip()
- name = self.info['name'].strip()
- if name and name != url:
- self.pyfile.name = name
-
- except Exception:
- pass
-
- try:
- folder = self.info['folder'] = self.pyfile.name
-
- except Exception:
- pass
-
- self.logDebug("File name: %s" % self.pyfile.name,
- "File folder: %s" % self.pyfile.name)
-
-
- 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.
- """
- url_p = urlparse.urlparse(self.pyfile.url)
- baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
-
- return [urlparse.urljoin(baseurl, link) if not urlparse.urlparse(link).scheme else link \
- for link in re.findall(self.LINK_PATTERN, self.html)]
-
-
- def handlePages(self, pyfile):
- try:
- pages = int(re.search(self.PAGES_PATTERN, self.html).group(1))
- except Exception:
- pages = 1
-
- for p in xrange(2, pages + 1):
- self.html = self.loadPage(p)
- self.links += self.getLinks()
diff --git a/module/plugins/internal/SimpleDereferer.py b/module/plugins/internal/SimpleDereferer.py
deleted file mode 100644
index fad1559c7..000000000
--- a/module/plugins/internal/SimpleDereferer.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import urllib
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.SimpleHoster import getFileURL, set_cookies
-
-
-class SimpleDereferer(Crypter):
- __name__ = "SimpleDereferer"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_subfolder" , "bool", "Save package to subfolder" , True),
- ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
-
- __description__ = """Simple dereferer plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- """
- Following patterns should be defined by each crypter:
-
- LINK_PATTERN: Regex to catch the redirect url in group(1)
- example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
-
- OFFLINE_PATTERN: (optional) Checks if the page is unreachable
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
- example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
-
-
- You can override the getLinks method if you need a more sophisticated way to extract the redirect url.
- """
-
- LINK_PATTERN = None
-
- TEXT_ENCODING = False
- COOKIES = True
-
-
- def decrypt(self, pyfile):
- link = getFileURL(self, pyfile.url)
-
- if not link:
- try:
- link = urllib.unquote(re.match(self.__pattern__, pyfile.url).group('LINK'))
-
- except Exception:
- self.prepare()
- self.preload()
- self.checkStatus()
-
- link = self.getLink()
-
- if link.strip():
- self.urls = [link.strip()] #@TODO: Remove `.strip()` in 0.4.10
-
- elif not self.urls and not self.packages: #@TODO: Remove in 0.4.10
- self.fail(_("No link grabbed"))
-
-
- def prepare(self):
- self.info = {}
- self.html = ""
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
-
- def preload(self):
- self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
-
- if isinstance(self.TEXT_ENCODING, basestring):
- self.html = unicode(self.html, self.TEXT_ENCODING)
-
-
- def checkStatus(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 getLink(self):
- try:
- return re.search(self.LINK_PATTERN, self.html).group(1)
-
- except Exception:
- pass
diff --git a/module/plugins/internal/SimpleHoster.py b/module/plugins/internal/SimpleHoster.py
deleted file mode 100644
index 0039d3f8e..000000000
--- a/module/plugins/internal/SimpleHoster.py
+++ /dev/null
@@ -1,762 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import datetime
-import mimetypes
-import os
-import re
-import time
-import urllib
-import urlparse
-
-from module.PyFile import statusMap as _statusMap
-from module.network.CookieJar import CookieJar
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import Fail, Retry
-from module.utils import fixup, fs_encode, parseFileSize
-
-
-#@TODO: Adapt and move to PyFile in 0.4.10
-statusMap = dict((v, k) for k, v in _statusMap.iteritems())
-
-
-#@TODO: Remove in 0.4.10 and redirect to self.error instead
-def _error(self, reason, type):
- if not reason and not type:
- type = "unknown"
-
- msg = _("%s error") % type.strip().capitalize() if type else _("Error")
- msg += (": %s" % reason.strip()) if reason else ""
- msg += _(" | Plugin may be out of date")
-
- raise Fail(msg)
-
-
-#@TODO: Remove in 0.4.10
-def _wait(self, seconds, reconnect):
- if seconds:
- self.setWait(int(seconds) + 1)
-
- if reconnect is not None:
- self.wantReconnect = reconnect
-
- super(SimpleHoster, self).wait()
-
-
-def replace_patterns(string, ruleslist):
- for r in ruleslist:
- rf, rt = r
- string = re.sub(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={}):
- 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 input_names:
- # check input attributes
- for key, val in input_names.iteritems():
- 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
-
-
-#: Deprecated
-def parseFileInfo(plugin, url="", html=""):
- if hasattr(plugin, "getInfo"):
- info = plugin.getInfo(url, html)
- res = info['name'], info['size'], info['status'], info['url']
- else:
- url = urllib.unquote(url)
- url_p = urlparse.urlparse(url)
- res = ((url_p.path.split('/')[-1]
- or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
- or url_p.netloc.split('.', 1)[0]),
- 0,
- 3 if url else 8,
- url)
-
- return res
-
-
-#@TODO: Remove in 0.4.10
-#@NOTE: Every plugin must have own parseInfos classmethod to work with 0.4.10
-def create_getInfo(plugin):
-
- def generator(list):
- for x in list:
- yield x
-
- if hasattr(plugin, "parseInfos"):
- fn = lambda urls: generator((info['name'], info['size'], info['status'], info['url']) for info in plugin.parseInfos(urls))
- else:
- fn = lambda urls: generator(parseFileInfo(url) for url in urls)
-
- return fn
-
-
-def timestamp():
- return int(time.time() * 1000)
-
-
-#@TODO: Move to hoster class in 0.4.10
-def getFileURL(self, url, follow_location=None):
- link = ""
- redirect = 1
-
- if type(follow_location) is int:
- redirect = max(follow_location, 1)
- else:
- redirect = 10
-
- for i in xrange(redirect):
- try:
- self.logDebug("Redirect #%d to: %s" % (i, url))
- header = self.load(url, just_header=True, decode=True)
-
- except Exception: #: Bad bad bad...
- req = pyreq.getHTTPRequest()
- res = req.load(url, just_header=True, decode=True)
-
- req.close()
-
- header = {"code": 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
-
- if 'content-disposition' in header:
- link = url
-
- elif 'location' in header and header['location'].strip():
- location = header['location']
-
- if not urlparse.urlparse(location).scheme:
- url_p = urlparse.urlparse(url)
- baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
- location = urlparse.urljoin(baseurl, location)
-
- if 'code' in header and header['code'] == 302:
- link = location
-
- if follow_location:
- url = location
- continue
-
- else:
- extension = os.path.splitext(urlparse.urlparse(url).path.split('/')[-1])[-1]
-
- if 'content-type' in header and header['content-type'].strip():
- mimetype = header['content-type'].split(';')[0].strip()
-
- elif extension:
- mimetype = mimetypes.guess_type(extension, False)[0] or "application/octet-stream"
-
- else:
- mimetype = ""
-
- if mimetype and (link or 'html' not in mimetype):
- link = url
- else:
- link = ""
-
- break
-
- else:
- try:
- self.logError(_("Too many redirects"))
- except Exception:
- pass
-
- return link
-
-
-def secondsToMidnight(gmt=0):
- now = datetime.datetime.utcnow() + datetime.timedelta(hours=gmt)
-
- if now.hour is 0 and now.minute < 10:
- midnight = now
- else:
- midnight = now + datetime.timedelta(days=1)
-
- td = midnight.replace(hour=0, minute=10, second=0, microsecond=0) - now
-
- if hasattr(td, 'total_seconds'):
- res = td.total_seconds()
- else: #@NOTE: work-around for python 2.5 and 2.6 missing datetime.timedelta.total_seconds
- res = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
-
- return int(res)
-
-
-class SimpleHoster(Hoster):
- __name__ = "SimpleHoster"
- __type__ = "hoster"
- __version__ = "1.42"
-
- __pattern__ = r'^unmatchable$'
- __config__ = [("use_premium", "bool", "Use premium account if available" , True),
- ("fallback" , "bool", "Fallback to free download if premium fails", True)]
-
- __description__ = """Simple hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- """
- Info patterns should be defined by each hoster:
-
- INFO_PATTERN: (optional) Name and Size of the file
- example: INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)'
- or
- NAME_PATTERN: (optional) Name that will be set for the file
- example: NAME_PATTERN = r'(?P<N>file_name)'
- SIZE_PATTERN: (optional) Size that will be checked for the file
- example: SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)'
-
- HASHSUM_PATTERN: (optional) Hash code and type of the file
- example: HASHSUM_PATTERN = r'(?P<H>hash_code) (?P<T>MD5)'
-
- OFFLINE_PATTERN: (optional) Check if the page is unreachable
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: (optional) Check if the page is temporarily unreachable
- example: TEMP_OFFLINE_PATTERN = r'Server (maintenance|maintainance)'
-
-
- Error handling patterns are all optional:
-
- WAIT_PATTERN: (optional) Detect waiting time
- example: WAIT_PATTERN = r''
-
- PREMIUM_ONLY_PATTERN: (optional) Check if the file can be downloaded only with a premium account
- example: PREMIUM_ONLY_PATTERN = r'Premium account required'
-
- ERROR_PATTERN: (optional) Detect any error preventing download
- example: ERROR_PATTERN = r''
-
-
- Instead overriding handleFree and handlePremium methods you can define the following patterns for direct download:
-
- LINK_FREE_PATTERN: (optional) group(1) should be the direct link for free download
- example: LINK_FREE_PATTERN = r'<div class="link"><a href="(.+?)"'
-
- LINK_PREMIUM_PATTERN: (optional) group(1) should be the direct link for premium download
- example: LINK_PREMIUM_PATTERN = r'<div class="link"><a href="(.+?)"'
- """
-
- NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
- SIZE_REPLACEMENTS = []
- URL_REPLACEMENTS = []
-
- TEXT_ENCODING = False #: Set to True or encoding name if encoding value in http header is not correct
- COOKIES = True #: or False or list of tuples [(domain, name, value)]
- CHECK_TRAFFIC = False #: Set to True to force checking traffic left for premium account
- DIRECT_LINK = None #: Set to True to looking for direct link (as defined in handleDirect method), set to None to do it if self.account is True else False
- MULTI_HOSTER = False #: Set to True to leech other hoster link (as defined in handleMulti method)
- LOGIN_ACCOUNT = False #: Set to True to require account login
- DISPOSITION = True #: Set to True to use any content-disposition value in http header as file name
-
- directLink = getFileURL #@TODO: Remove in 0.4.10
-
-
- @classmethod
- def parseInfos(cls, urls): #@TODO: Built-in in 0.4.10 core (remove from plugins)
- for url in urls:
- url = replace_patterns(url, cls.URL_REPLACEMENTS)
- yield cls.getInfo(url)
-
-
- @classmethod
- def apiInfo(cls, url="", get={}, post={}):
- url = urllib.unquote(url)
- url_p = urlparse.urlparse(url)
- return {'name' : (url_p.path.split('/')[-1]
- or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
- or url_p.netloc.split('.', 1)[0]),
- 'size' : 0,
- 'status': 3 if url else 8,
- 'url' : url}
-
-
- @classmethod
- def getInfo(cls, url="", html=""):
- info = cls.apiInfo(url)
- online = False if info['status'] != 2 else True
-
- try:
- info['pattern'] = re.match(cls.__pattern__, url).groupdict() #: pattern groups will be saved here
-
- except Exception:
- info['pattern'] = {}
-
- if not html and not online:
- if not url:
- info['error'] = "missing url"
- info['status'] = 1
-
- elif info['status'] is 3 and not getFileURL(None, url):
- try:
- html = getURL(url, cookies=cls.COOKIES, decode=not cls.TEXT_ENCODING)
-
- if isinstance(cls.TEXT_ENCODING, basestring):
- html = unicode(html, cls.TEXT_ENCODING)
-
- except BadHeader, e:
- info['error'] = "%d: %s" % (e.code, e.content)
-
- if e.code is 404:
- info['status'] = 1
-
- elif e.code is 503:
- info['status'] = 6
-
- if html:
- if hasattr(cls, "OFFLINE_PATTERN") and re.search(cls.OFFLINE_PATTERN, html):
- info['status'] = 1
-
- elif hasattr(cls, "TEMP_OFFLINE_PATTERN") and re.search(cls.TEMP_OFFLINE_PATTERN, html):
- info['status'] = 6
-
- else:
- for pattern in ("INFO_PATTERN", "NAME_PATTERN", "SIZE_PATTERN", "HASHSUM_PATTERN"):
- try:
- attr = getattr(cls, pattern)
- pdict = re.search(attr, html).groupdict()
-
- if all(True for k in pdict if k not in info['pattern']):
- info['pattern'].update(pdict)
-
- except AttributeError:
- continue
-
- else:
- online = True
-
- if online:
- info['status'] = 2
-
- if 'N' in info['pattern']:
- info['name'] = replace_patterns(urllib.unquote(info['pattern']['N'].strip()),
- cls.NAME_REPLACEMENTS)
-
- if 'S' in info['pattern']:
- size = replace_patterns(info['pattern']['S'] + info['pattern']['U'] if 'U' in info['pattern'] else info['pattern']['S'],
- cls.SIZE_REPLACEMENTS)
- info['size'] = parseFileSize(size)
-
- elif isinstance(info['size'], basestring):
- unit = info['units'] if 'units' in info else None
- info['size'] = parseFileSize(info['size'], unit)
-
- if 'H' in info['pattern']:
- hashtype = info['pattern']['T'] if 'T' in info['pattern'] else "hash"
- info[hashtype] = info['pattern']['H']
-
- if not info['pattern']:
- info.pop('pattern', None)
-
- return info
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
-
-
- def prepare(self):
- self.pyfile.error = "" #@TODO: Remove in 0.4.10
-
- self.info = {}
- self.html = ""
- self.link = "" #@TODO: Move to hoster class in 0.4.10
- self.directDL = False #@TODO: Move to hoster class in 0.4.10
- self.multihost = False #@TODO: Move to hoster class in 0.4.10
-
- if not self.getConfig('use_premium', True):
- self.retryFree()
-
- if self.LOGIN_ACCOUNT and not self.account:
- self.fail(_("Required account not found"))
-
- self.req.setOption("timeout", 120)
-
- if isinstance(self.COOKIES, list):
- set_cookies(self.req.cj, self.COOKIES)
-
- if (self.MULTI_HOSTER
- and (self.__pattern__ != self.core.pluginManager.hosterPlugins[self.__name__]['pattern']
- or re.match(self.__pattern__, self.pyfile.url) is None)):
- self.multihost = True
- return
-
- if self.DIRECT_LINK is None:
- self.directDL = bool(self.account)
- else:
- self.directDL = self.DIRECT_LINK
-
- self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
-
-
- def preload(self):
- self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
-
- if isinstance(self.TEXT_ENCODING, basestring):
- self.html = unicode(self.html, self.TEXT_ENCODING)
-
-
- def process(self, pyfile):
- try:
- self.prepare()
- self.checkInfo()
-
- if self.directDL:
- self.logDebug("Looking for direct download link...")
- self.handleDirect(pyfile)
-
- if self.multihost and not self.link and not self.lastDownload:
- self.logDebug("Looking for leeched download link...")
- self.handleMulti(pyfile)
-
- if not self.link and not self.lastDownload:
- self.MULTI_HOSTER = False
- self.retry(1, reason="Multi hoster fails")
-
- if not self.link and not self.lastDownload:
- self.preload()
- self.checkInfo()
-
- if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as premium download")
- self.handlePremium(pyfile)
-
- elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.logDebug("Handled as free download")
- self.handleFree(pyfile)
-
- self.downloadLink(self.link, self.DISPOSITION)
- self.checkFile()
-
- except Fail, e: #@TODO: Move to PluginThread in 0.4.10
- if self.getConfig('fallback', True) and self.premium:
- self.logWarning(_("Premium download failed"), e)
- self.retryFree()
- else:
- raise Fail(e)
-
-
- def downloadLink(self, link, disposition=True):
- if link and isinstance(link, basestring):
- self.correctCaptcha()
-
- if not urlparse.urlparse(link).scheme:
- url_p = urlparse.urlparse(self.pyfile.url)
- baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
- link = urlparse.urljoin(baseurl, link)
-
- self.download(link, ref=False, disposition=disposition)
-
-
- def checkFile(self, rules={}):
- if self.cTask and not self.lastDownload:
- self.invalidCaptcha()
- self.retry(10, reason=_("Wrong captcha"))
-
- elif not self.lastDownload or not os.path.exists(fs_encode(self.lastDownload)):
- self.lastDownload = ""
- self.error(self.pyfile.error or _("No file downloaded"))
-
- else:
- errmsg = self.checkDownload({'Empty file': re.compile(r'\A\s*\Z'),
- 'Html error': re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)')})
-
- if not errmsg:
- for r, p in [('Html file' , re.compile(r'\A\s*<!DOCTYPE html') ),
- ('Request error', re.compile(r'([Aa]n error occured while processing your request)'))]:
- if r not in rules:
- rules[r] = p
-
- for r, a in [('Error' , "ERROR_PATTERN" ),
- ('Premium only', "PREMIUM_ONLY_PATTERN"),
- ('Wait error' , "WAIT_PATTERN" )]:
- if r not in rules and hasattr(self, a):
- rules[r] = getattr(self, a)
-
- errmsg = self.checkDownload(rules)
-
- if not errmsg:
- return
-
- errmsg = errmsg.strip().capitalize()
-
- try:
- errmsg += " | " + self.lastCheck.group(1).strip()
- except Exception:
- pass
-
- self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
- self.retry(3, 60, errmsg)
-
-
- def checkErrors(self):
- if not self.html:
- self.logWarning(_("No html code to check"))
- return
-
- if hasattr(self, 'PREMIUM_ONLY_PATTERN') and not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
- self.fail(_("Link require a premium account to be handled"))
-
- elif hasattr(self, 'ERROR_PATTERN'):
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- try:
- errmsg = m.group(1).strip()
- except Exception:
- errmsg = m.group(0).strip()
-
- self.info['error'] = errmsg
-
- if "hour" in errmsg:
- self.wait(1 * 60 * 60, True)
-
- elif re.search("da(il)?y|today", errmsg):
- self.wait(secondsToMidnight(gmt=2), True)
-
- elif "minute" in errmsg:
- self.wait(1 * 60)
-
- else:
- self.error(errmsg)
-
- elif hasattr(self, 'WAIT_PATTERN'):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- try:
- waitmsg = m.group(1).strip()
- except Exception:
- waitmsg = m.group(0).strip()
-
- wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
- re.findall(r'(\d+)\s*(hr|hour|min|sec|)', waitmsg, re.I))
- self.wait(wait_time, wait_time > 300)
-
- self.info.pop('error', None)
-
-
- def checkStatus(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("Update file info...")
- self.logDebug("Previous file info: %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("Current file info: %s" % self.info)
-
- try:
- status = self.info['status']
-
- if status is 1:
- self.offline()
-
- elif status is 6:
- self.tempOffline()
-
- elif status is 8:
- self.fail(self.info['error'] if 'error' in self.info else "Failed")
-
- finally:
- self.logDebug("File status: %s" % statusMap[status])
-
-
- def checkNameSize(self, getinfo=True):
- if not self.info or getinfo:
- self.logDebug("Update file info...")
- self.logDebug("Previous file info: %s" % self.info)
- self.info.update(self.getInfo(self.pyfile.url, self.html))
- self.logDebug("Current file info: %s" % self.info)
-
- try:
- url = self.info['url'].strip()
- name = self.info['name'].strip()
- if name and name != url:
- self.pyfile.name = name
-
- except Exception:
- pass
-
- try:
- size = self.info['size']
- if size > 0:
- self.pyfile.size = size
-
- except Exception:
- pass
-
- self.logDebug("File name: %s" % self.pyfile.name,
- "File size: %s byte" % self.pyfile.size if self.pyfile.size > 0 else "File size: Unknown")
-
-
- def checkInfo(self):
- self.checkNameSize()
-
- if self.html:
- self.checkErrors()
- self.checkNameSize()
-
- self.checkStatus(getinfo=False)
-
-
- #: Deprecated
- def getFileInfo(self):
- self.info = {}
- self.checkInfo()
- return self.info
-
-
- def handleDirect(self, pyfile):
- link = self.directLink(pyfile.url, self.resumeDownload)
-
- if link:
- self.logInfo(_("Direct download link detected"))
- self.link = link
- else:
- self.logDebug("Direct download link not found")
-
-
- def handleMulti(self, pyfile): #: Multi-hoster handler
- pass
-
-
- def handleFree(self, pyfile):
- if not hasattr(self, 'LINK_FREE_PATTERN'):
- self.logError(_("Free download not implemented"))
-
- m = re.search(self.LINK_FREE_PATTERN, self.html)
- if m is None:
- self.error(_("Free download link not found"))
- else:
- self.link = m.group(1)
-
-
- def handlePremium(self, pyfile):
- if not hasattr(self, 'LINK_PREMIUM_PATTERN'):
- self.logError(_("Premium download not implemented"))
- self.logDebug("Handled as free download")
- self.handleFree(pyfile)
-
- m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
- if m is None:
- self.error(_("Premium download link not found"))
- else:
- self.link = m.group(1)
-
-
- 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.wait(wait_time, True)
- self.retry(max_tries=max_tries, reason=_("Download limit reached"))
-
-
- def parseHtmlForm(self, attr_str="", input_names={}):
- return parseHtmlForm(attr_str, self.html, input_names)
-
-
- def checkTrafficLeft(self):
- if not self.account:
- return True
-
- traffic = self.account.getAccountInfo(self.user, True)['trafficleft']
-
- if traffic is None:
- return False
- elif traffic == -1:
- return True
- else:
- size = self.pyfile.size / 1024
- self.logInfo(_("Filesize: %i KiB, Traffic left for user %s: %i KiB") % (size, self.user, traffic))
- return size <= traffic
-
-
- def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
- """getConfig with default value - sublass may not implements all config options"""
- try:
- return self.getConf(option)
-
- except KeyError:
- return default
-
-
- def retryFree(self):
- if not self.premium:
- return
- self.premium = False
- self.account = None
- self.req = self.core.requestFactory.getRequest(self.__name__)
- self.retries = -1
- raise Retry(_("Fallback to free download"))
-
-
- #@TODO: Remove in 0.4.10
- def wait(self, seconds=0, reconnect=None):
- return _wait(self, seconds, reconnect)
-
-
- def error(self, reason="", type="parse"):
- return _error(self, reason, type)
diff --git a/module/plugins/internal/UnRar.py b/module/plugins/internal/UnRar.py
deleted file mode 100644
index 5b9f2e1c3..000000000
--- a/module/plugins/internal/UnRar.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import subprocess
-
-from glob import glob
-from string import digits
-
-from module.plugins.internal.Extractor import Extractor, ArchiveError, CRCError, PasswordError
-from module.utils import fs_decode, fs_encode, save_join
-
-
-def renice(pid, value):
- if value and os.name != "nt":
- try:
- subprocess.Popen(["renice", str(value), str(pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
-
- except Exception:
- pass
-
-
-class UnRar(Extractor):
- __name__ = "UnRar"
- __version__ = "1.20"
-
- __description__ = """Rar extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
- ("Walter Purcaro", "vuolter@gmail.com"),
- ("Immenz" , "immenz@gmx.net" )]
-
-
- CMD = "unrar"
- VERSION = ""
- EXTENSIONS = [".rar"]
-
-
- re_multipart = re.compile(r'\.(part|r)(\d+)(?:\.rar)?(\.rev|\.bad)?',re.I)
-
- re_filefixed = re.compile(r'Building (.+)')
- re_filelist = re.compile(r'^(.)(\s*[\w\.\-]+)\s+(\d+\s+)+(?:\d+\%\s+)?[\d\-]{8}\s+[\d\:]{5}', re.M|re.I)
-
- re_wrongpwd = re.compile(r'password', re.I)
- re_wrongcrc = re.compile(r'encrypted|damaged|CRC failed|checksum error|corrupt', re.I)
-
- re_version = re.compile(r'(?:UN)?RAR\s(\d+\.\d+)', re.I)
-
-
- @classmethod
- def isUsable(cls):
- if os.name == "nt":
- try:
- cls.CMD = os.path.join(pypath, "RAR.exe")
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- cls.__name__ = "RAR"
- cls.REPAIR = True
-
- except OSError:
- cls.CMD = os.path.join(pypath, "UnRAR.exe")
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- else:
- try:
- p = subprocess.Popen(["rar"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- cls.__name__ = "RAR"
- cls.REPAIR = True
-
- except OSError: #: fallback to unrar
- p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
-
- m = cls.re_version.search(out)
- cls.VERSION = m.group(1) if m else '(version unknown)'
-
- return True
-
-
- @classmethod
- def isMultipart(cls, filename):
- return True if cls.re_multipart.search(filename) else False
-
-
- def verify(self, password):
- p = self.call_cmd("t", "-v", fs_encode(self.filename), password=password)
- self._progress(p)
- err = p.stderr.read().strip()
-
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- if self.re_wrongcrc.search(err):
- raise CRCError(err)
-
-
- def check(self, password):
- p = self.call_cmd("l", "-v", fs_encode(self.filename), password=password)
- out, err = p.communicate()
-
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- if self.re_wrongcrc.search(err):
- raise CRCError(err)
-
- # output only used to check if passworded files are present
- for attr in self.re_filelist.findall(out):
- if attr[0].startswith("*"):
- raise PasswordError
-
-
- def repair(self):
- p = self.call_cmd("rc", fs_encode(self.filename))
-
- # communicate and retrieve stderr
- self._progress(p)
- err = p.stderr.read().strip()
- if err or p.returncode:
- return False
- return True
-
-
- def _progress(self, process):
- s = ""
- while True:
- c = process.stdout.read(1)
- # quit loop on eof
- if not c:
- break
- # reading a percentage sign -> set progress and restart
- if c == '%':
- self.notifyProgress(int(s))
- s = ""
- # not reading a digit -> therefore restart
- elif c not in digits:
- s = ""
- # add digit to progressstring
- else:
- s += c
-
-
- def extract(self, password=None):
- command = "x" if self.fullpath else "e"
-
- p = self.call_cmd(command, fs_encode(self.filename), self.out, password=password)
-
- renice(p.pid, self.renice)
-
- # communicate and retrieve stderr
- self._progress(p)
- err = p.stderr.read().strip()
-
- if err:
- if self.re_wrongpwd.search(err):
- raise PasswordError
-
- elif self.re_wrongcrc.search(err):
- raise CRCError(err)
-
- else: #: raise error if anything is on stderr
- raise ArchiveError(err)
-
- if p.returncode:
- raise ArchiveError(_("Process return code: %d") % p.returncode)
-
- self.files = self.list(password)
-
-
- def getDeleteFiles(self):
- dir, name = os.path.split(self.filename)
-
- # actually extracted file
- files = [self.filename]
-
- # eventually Multipart Files
- files.extend(save_join(dir, os.path.basename(file)) for file in filter(self.isMultipart, os.listdir(dir))
- if re.sub(self.re_multipart,".rar",name) == re.sub(self.re_multipart,".rar",file))
-
- return files
-
-
- def list(self, password=None):
- command = "vb" if self.fullpath else "lb"
-
- p = self.call_cmd(command, "-v", fs_encode(self.filename), password=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.manager.logError(err.strip())
-
- result = set()
- if not self.fullpath and self.VERSION.startswith('5'):
- # NOTE: Unrar 5 always list full path
- for f in fs_decode(out).splitlines():
- f = save_join(self.out, os.path.basename(f.strip()))
- if os.path.isfile(f):
- result.add(save_join(self.out, os.path.basename(f)))
- else:
- for f in fs_decode(out).splitlines():
- f = f.strip()
- result.add(save_join(self.out, f))
-
- return list(result)
-
-
- def call_cmd(self, command, *xargs, **kwargs):
- args = []
-
- # overwrite flag
- if self.overwrite:
- args.append("-o+")
- else:
- args.append("-o-")
- if self.delete != 'No':
- args.append("-or")
-
- for word in self.excludefiles:
- args.append("-x'%s'" % word.strip())
-
- # 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-")
-
- if self.keepbroken:
- args.append("-kb")
-
- # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
- call = [self.CMD, command] + args + list(xargs)
-
- self.manager.logDebug(" ".join(call))
-
- p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return p
diff --git a/module/plugins/internal/UnZip.py b/module/plugins/internal/UnZip.py
deleted file mode 100644
index 8d3fec370..000000000
--- a/module/plugins/internal/UnZip.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import os
-import sys
-import zipfile
-
-from module.plugins.internal.Extractor import Extractor, ArchiveError, CRCError, PasswordError
-from module.utils import fs_encode
-
-
-class UnZip(Extractor):
- __name__ = "UnZip"
- __version__ = "1.12"
-
- __description__ = """Zip extractor plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- EXTENSIONS = [".zip", ".zip64"]
- VERSION ="(python %s.%s.%s)" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
-
-
- @classmethod
- def isUsable(cls):
- return sys.version_info[:2] >= (2, 6)
-
-
- def list(self, password=None):
- with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
- z.setpassword(password)
- return z.namelist()
-
-
- def check(self, password):
- pass
-
-
- def verify(self):
- with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
- badfile = z.testzip()
-
- if badfile:
- raise CRCError(badfile)
- else:
- raise PasswordError
-
-
- def extract(self, password=None):
- try:
- with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
- z.setpassword(password)
-
- badfile = z.testzip()
-
- if badfile:
- raise CRCError(badfile)
- else:
- z.extractall(self.out)
-
- except (zipfile.BadZipfile, zipfile.LargeZipFile), e:
- raise ArchiveError(e)
-
- except RuntimeError, e:
- if "encrypted" in e:
- raise PasswordError
- else:
- raise ArchiveError(e)
- else:
- self.files = z.namelist()
diff --git a/module/plugins/internal/XFSAccount.py b/module/plugins/internal/XFSAccount.py
deleted file mode 100644
index 41e1bde4d..000000000
--- a/module/plugins/internal/XFSAccount.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-import urlparse
-
-from module.plugins.Account import Account
-from module.plugins.internal.SimpleHoster import parseHtmlForm, set_cookies
-
-
-class XFSAccount(Account):
- __name__ = "XFSAccount"
- __type__ = "account"
- __version__ = "0.36"
-
- __description__ = """XFileSharing account plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- HOSTER_DOMAIN = None
- HOSTER_URL = None
- LOGIN_URL = None
-
- COOKIES = True
-
- PREMIUM_PATTERN = r'\(Premium only\)'
-
- VALID_UNTIL_PATTERN = r'Premium.[Aa]ccount expire:.*?(\d{1,2} [\w^_]+ \d{4})'
-
- TRAFFIC_LEFT_PATTERN = r'Traffic available today:.*?<b>\s*(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
- TRAFFIC_LEFT_UNIT = "MB" #: used only if no group <U> was found
-
- LEECH_TRAFFIC_PATTERN = r'Leech Traffic left:<b>.*?(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
- LEECH_TRAFFIC_UNIT = "MB" #: used only if no group <U> was found
-
- LOGIN_FAIL_PATTERN = r'Incorrect Login or Password|account was banned|Error<'
-
-
- def __init__(self, manager, accounts): #@TODO: remove in 0.4.10
- self.init()
- return super(XFSAccount, self).__init__(manager, accounts)
-
-
- def init(self):
- if not self.HOSTER_DOMAIN:
- self.logError(_("Missing HOSTER_DOMAIN"))
- self.COOKIES = False
-
- else:
- if not self.HOSTER_URL:
- self.HOSTER_URL = "http://www.%s/" % self.HOSTER_DOMAIN
-
- if isinstance(self.COOKIES, list):
- self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
- set_cookies(req.cj, self.COOKIES)
-
-
- def loadAccountInfo(self, user, req):
- validuntil = None
- trafficleft = None
- leechtraffic = None
- premium = None
-
- if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
- return {'validuntil' : validuntil,
- 'trafficleft' : trafficleft,
- 'leechtraffic': leechtraffic,
- 'premium' : premium}
-
- html = req.load(self.HOSTER_URL, get={'op': "my_account"}, decode=True)
-
- premium = True if re.search(self.PREMIUM_PATTERN, html) else False
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- expiredate = m.group(1).strip()
- self.logDebug("Expire date: " + expiredate)
-
- try:
- validuntil = time.mktime(time.strptime(expiredate, "%d %B %Y"))
-
- except Exception, e:
- self.logError(e)
-
- else:
- self.logDebug("Valid until: %s" % validuntil)
-
- if validuntil > time.mktime(time.gmtime()):
- premium = True
- trafficleft = -1
- else:
- premium = False
- validuntil = None #: registered account type (not premium)
- else:
- self.logDebug("VALID_UNTIL_PATTERN not found")
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- try:
- traffic = m.groupdict()
- size = traffic['S']
-
- if "nlimited" in size:
- trafficleft = -1
- if validuntil is None:
- validuntil = -1
- else:
- if 'U' in traffic:
- unit = traffic['U']
- elif isinstance(self.TRAFFIC_LEFT_UNIT, basestring):
- unit = self.TRAFFIC_LEFT_UNIT
- else:
- unit = ""
-
- trafficleft = self.parseTraffic(size + unit)
-
- except Exception, e:
- self.logError(e)
- else:
- self.logDebug("TRAFFIC_LEFT_PATTERN not found")
-
- leech = [m.groupdict() for m in re.finditer(self.LEECH_TRAFFIC_PATTERN, html)]
- if leech:
- leechtraffic = 0
- try:
- for traffic in leech:
- size = traffic['S']
-
- if "nlimited" in size:
- leechtraffic = -1
- if validuntil is None:
- validuntil = -1
- break
- else:
- if 'U' in traffic:
- unit = traffic['U']
- elif isinstance(self.LEECH_TRAFFIC_UNIT, basestring):
- unit = self.LEECH_TRAFFIC_UNIT
- else:
- unit = ""
-
- leechtraffic += self.parseTraffic(size + unit)
-
- except Exception, e:
- self.logError(e)
- else:
- self.logDebug("LEECH_TRAFFIC_PATTERN not found")
-
- return {'validuntil' : validuntil,
- 'trafficleft' : trafficleft,
- 'leechtraffic': leechtraffic,
- 'premium' : premium}
-
-
- def login(self, user, data, req):
- if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
- raise Exception(_("Missing HOSTER_DOMAIN"))
-
- if not self.LOGIN_URL:
- self.LOGIN_URL = urlparse.urljoin(self.HOSTER_URL, "login.html")
- html = req.load(self.LOGIN_URL, decode=True)
-
- action, inputs = parseHtmlForm('name="FL"', html)
- if not inputs:
- inputs = {'op' : "login",
- 'redirect': self.HOSTER_URL}
-
- inputs.update({'login' : user,
- 'password': data['password']})
-
- if not action:
- action = self.HOSTER_URL
- html = req.load(action, post=inputs, decode=True)
-
- if re.search(self.LOGIN_FAIL_PATTERN, html):
- self.wrongPassword()
diff --git a/module/plugins/internal/XFSCrypter.py b/module/plugins/internal/XFSCrypter.py
deleted file mode 100644
index 80eff53ea..000000000
--- a/module/plugins/internal/XFSCrypter.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter, create_getInfo
-
-
-class XFSCrypter(SimpleCrypter):
- __name__ = "XFSCrypter"
- __type__ = "crypter"
- __version__ = "0.09"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """XFileSharing decrypter plugin"""
- __license__ = "GPLv3"
- __authors__ = [("Walter Purcaro", "vuolter@gmail.com")]
-
-
- HOSTER_DOMAIN = None
-
- URL_REPLACEMENTS = [(r'&?per_page=\d+', ""), (r'[?/&]+$', ""), (r'(.+/[^?]+)$', r'\1?'), (r'$', r'&per_page=10000')]
-
- LINK_PATTERN = r'<(?:td|TD).*?>\s*(?:<.+>\s*)?<a href="(.+?)".*?>.+?(?:</a>)?\s*(?:<.+>\s*)?</(?:td|TD)>'
- NAME_PATTERN = r'<[Tt]itle>.*?\: (?P<N>.+) folder</[Tt]itle>'
-
- OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
- TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
-
-
- def prepare(self):
- if not self.HOSTER_DOMAIN:
- if self.account:
- account = self.account
- else:
- account_name = (self.__name__ + ".py").replace("Folder.py", "").replace(".py", "")
- account = self.pyfile.m.core.accountManager.getAccountPlugin(account_name)
-
- if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
- self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
- else:
- self.fail(_("Missing HOSTER_DOMAIN"))
-
- if isinstance(self.COOKIES, list):
- self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
-
- return super(XFSCrypter, self).prepare()
diff --git a/module/plugins/internal/XFSHoster.py b/module/plugins/internal/XFSHoster.py
deleted file mode 100644
index 8dab0bb4f..000000000
--- a/module/plugins/internal/XFSHoster.py
+++ /dev/null
@@ -1,323 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import random
-import re
-import urlparse
-
-from module.plugins.internal.CaptchaService import ReCaptcha, SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, secondsToMidnight
-from module.utils import html_unescape
-
-
-class XFSHoster(SimpleHoster):
- __name__ = "XFSHoster"
- __type__ = "hoster"
- __version__ = "0.47"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """XFileSharing hoster plugin"""
- __license__ = "GPLv3"
- __authors__ = [("zoidberg" , "zoidberg@mujmail.cz"),
- ("stickell" , "l.stickell@yahoo.it"),
- ("Walter Purcaro", "vuolter@gmail.com" )]
-
-
- HOSTER_DOMAIN = None
-
- TEXT_ENCODING = False
- DIRECT_LINK = None
- MULTI_HOSTER = True #@NOTE: Should be default to False for safe, but I'm lazy...
-
- NAME_PATTERN = r'(Filename[ ]*:[ ]*</b>(</td><td nowrap>)?|name="fname"[ ]+value="|<[\w^_]+ class="(file)?name">)\s*(?P<N>.+?)(\s*<|")'
- SIZE_PATTERN = r'(Size[ ]*:[ ]*</b>(</td><td>)?|File:.*>|</font>\s*\(|<[\w^_]+ class="size">)\s*(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
-
- OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
- TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
-
- WAIT_PATTERN = r'<span id="countdown_str".*>(\d+)</span>|id="countdown" value=".*?(\d+).*?"'
- PREMIUM_ONLY_PATTERN = r'>This file is available for Premium Users only'
- ERROR_PATTERN = r'(?:class=["\']err["\'].*?>|<[Cc]enter><b>|>Error</td>|>\(ERROR:)(?:\s*<.+?>\s*)*(.+?)(?:["\']|<|\))'
-
- LINK_LEECH_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
- LINK_PATTERN = None #: final download url pattern
-
- CAPTCHA_PATTERN = r'(https?://[^"\']+?/captchas?/[^"\']+)'
- CAPTCHA_BLOCK_PATTERN = r'>Enter code.*?<div.*?>(.+?)</div>'
- RECAPTCHA_PATTERN = None
- SOLVEMEDIA_PATTERN = None
-
- FORM_PATTERN = None
- FORM_INPUTS_MAP = None #: dict passed as input_names to parseHtmlForm
-
-
- def setup(self):
- self.chunkLimit = -1 if self.premium else 1
- self.resumeDownload = self.multiDL = self.premium
-
-
- def prepare(self):
- """ Initialize important variables """
- if not self.HOSTER_DOMAIN:
- if self.account:
- account = self.account
- else:
- account = self.pyfile.m.core.accountManager.getAccountPlugin(self.__name__)
-
- if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
- self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
- else:
- self.fail(_("Missing HOSTER_DOMAIN"))
-
- if isinstance(self.COOKIES, list):
- self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
-
- if not self.LINK_PATTERN:
- pattern = r'(https?://(?:www\.)?([^/]*?%s|\d+\.\d+\.\d+\.\d+)(\:\d+)?(/d/|(/files)?/\d+/\w+/).+?)["\'<]'
- self.LINK_PATTERN = pattern % self.HOSTER_DOMAIN.replace('.', '\.')
-
- self.captcha = None
- self.errmsg = None
-
- super(XFSHoster, self).prepare()
-
- if self.DIRECT_LINK is None:
- self.directDL = self.premium
-
-
- def handleFree(self, pyfile):
- for i in xrange(1, 6):
- self.logDebug("Getting download link: #%d" % i)
-
- self.checkErrors()
-
- m = re.search(self.LINK_PATTERN, self.html, re.S)
- if m:
- break
-
- data = self.getPostParameters()
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
-
- self.html = self.load(pyfile.url, post=data, decode=True)
-
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
- m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I)
- if m and not "op=" in m.group(1):
- break
-
- m = re.search(self.LINK_PATTERN, self.html, re.S)
- if m:
- break
- else:
- self.logError(data['op'] if 'op' in data else _("UNKNOWN"))
- return ""
-
- self.link = m.group(1).strip() #@TODO: Remove .strip() in 0.4.10
-
-
- def handlePremium(self, pyfile):
- return self.handleFree(pyfile)
-
-
- def handleMulti(self, pyfile):
- if not self.account:
- self.fail(_("Only registered or premium users can use url leech feature"))
-
- #only tested with easybytez.com
- self.html = self.load("http://www.%s/" % self.HOSTER_DOMAIN)
-
- action, inputs = self.parseHtmlForm()
-
- upload_id = "%012d" % int(random.random() * 10 ** 12)
- action += upload_id + "&js_on=1&utype=prem&upload_type=url"
-
- inputs['tos'] = '1'
- inputs['url_mass'] = pyfile.url
- inputs['up1oad_type'] = 'url'
-
- self.logDebug(action, inputs)
-
- self.req.setOption("timeout", 600) #: wait for file to upload to easybytez.com
-
- self.html = self.load(action, post=inputs)
-
- self.checkErrors()
-
- action, inputs = self.parseHtmlForm('F1')
- if not inputs:
- self.retry(reason=self.errmsg or _("TEXTAREA F1 not found"))
-
- self.logDebug(inputs)
-
- stmsg = inputs['st']
-
- if stmsg == 'OK':
- self.html = self.load(action, post=inputs)
-
- elif 'Can not leech file' in stmsg:
- self.retry(20, 3 * 60, _("Can not leech file"))
-
- elif 'today' in stmsg:
- self.retry(wait_time=secondsToMidnight(gmt=2), reason=_("You've used all Leech traffic today"))
-
- else:
- self.fail(stmsg)
-
- #get easybytez.com link for uploaded file
- m = re.search(self.LINK_LEECH_PATTERN, self.html)
- if m is None:
- self.error(_("LINK_LEECH_PATTERN not found"))
-
- header = self.load(m.group(1), just_header=True, decode=True)
-
- if 'location' in header: #: Direct download link
- self.link = header['location']
-
-
- def checkErrors(self):
- m = re.search(self.ERROR_PATTERN, self.html)
- if m is None:
- self.errmsg = None
- else:
- self.errmsg = m.group(1).strip()
-
- self.logWarning(re.sub(r"<.*?>", " ", self.errmsg))
-
- if 'wait' in self.errmsg:
- wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
- re.findall(r'(\d+)\s*(hr|hour|min|sec|)', self.errmsg, re.I))
- self.wait(wait_time, wait_time > 300)
-
- elif 'country' in self.errmsg:
- self.fail(_("Downloads are disabled for your country"))
-
- 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:
- if 'day' in self.errmsg:
- delay = secondsToMidnight(gmt=2)
- retries = 3
- else:
- delay = 1 * 60 * 60
- retries = 24
-
- self.wantReconnect = True
- self.retry(retries, delay, _("Download limit exceeded"))
-
- elif 'countdown' in self.errmsg or 'Expired' in self.errmsg:
- self.retry(reason=_("Link expired"))
-
- elif 'maintenance' in self.errmsg or 'maintainance' in self.errmsg:
- self.tempOffline()
-
- elif 'up to' in self.errmsg:
- self.fail(_("File too large for free download"))
-
- else:
- self.wantReconnect = True
- self.retry(wait_time=60, reason=self.errmsg)
-
- if self.errmsg:
- self.info['error'] = self.errmsg
- else:
- self.info.pop('error', None)
-
-
- def getPostParameters(self):
- if self.FORM_PATTERN or self.FORM_INPUTS_MAP:
- action, inputs = self.parseHtmlForm(self.FORM_PATTERN or "", self.FORM_INPUTS_MAP or {})
- else:
- action, inputs = self.parseHtmlForm(input_names={'op': re.compile(r'^download')})
-
- if not inputs:
- action, inputs = self.parseHtmlForm('F1')
- if not inputs:
- self.retry(reason=self.errmsg or _("TEXTAREA F1 not found"))
-
- self.logDebug(inputs)
-
- if 'op' in inputs:
- if "password" in inputs:
- password = self.getPassword()
- if password:
- inputs['password'] = password
- else:
- self.fail(_("Missing password"))
-
- if not self.premium:
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait_time = int(m.group(1))
- self.setWait(wait_time, False)
-
- self.captcha = self.handleCaptcha(inputs)
-
- self.wait()
- else:
- inputs['referer'] = self.pyfile.url
-
- if self.premium:
- inputs['method_premium'] = "Premium Download"
- inputs.pop('method_free', None)
- else:
- inputs['method_free'] = "Free Download"
- inputs.pop('method_premium', None)
-
- return inputs
-
-
- def handleCaptcha(self, inputs):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- captcha_url = m.group(1)
- inputs['code'] = self.decryptCaptcha(captcha_url)
- return 1
-
- m = re.search(self.CAPTCHA_BLOCK_PATTERN, self.html, re.S)
- if m:
- captcha_div = m.group(1)
- numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
-
- self.logDebug(captcha_div)
-
- inputs['code'] = "".join(a[1] for a in sorted(numerals, key=lambda num: int(num[0])))
-
- self.logDebug("Captcha code: %s" % inputs['code'], numerals)
- return 2
-
- recaptcha = ReCaptcha(self)
- try:
- captcha_key = re.search(self.RECAPTCHA_PATTERN, self.html).group(1)
-
- except Exception:
- captcha_key = recaptcha.detect_key()
-
- else:
- self.logDebug("ReCaptcha key: %s" % captcha_key)
-
- if captcha_key:
- inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge(captcha_key)
- return 3
-
- solvemedia = SolveMedia(self)
- try:
- captcha_key = re.search(self.SOLVEMEDIA_PATTERN, self.html).group(1)
-
- except Exception:
- captcha_key = solvemedia.detect_key()
-
- else:
- self.logDebug("SolveMedia key: %s" % captcha_key)
-
- if captcha_key:
- inputs['adcopy_response'], inputs['adcopy_challenge'] = solvemedia.challenge(captcha_key)
- return 4
-
- return 0
diff --git a/module/plugins/internal/__init__.py b/module/plugins/internal/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/plugins/internal/__init__.py
+++ /dev/null
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/Processor.py b/module/remote/thriftbackend/Processor.py
deleted file mode 100644
index a8b87c82c..000000000
--- a/module/remote/thriftbackend/Processor.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from thriftgen.pyload import Pyload
-
-class Processor(Pyload.Processor):
- def __init__(self, *args, **kwargs):
- Pyload.Processor.__init__(self, *args, **kwargs)
- self.authenticated = {}
-
- def process(self, iprot, oprot):
- trans = oprot.trans
- if trans not in self.authenticated:
- self.authenticated[trans] = False
- oldclose = trans.close
-
- def wrap():
- if self in self.authenticated:
- del self.authenticated[trans]
- oldclose()
-
- trans.close = wrap
- authenticated = self.authenticated[trans]
- (name, type, seqid) = iprot.readMessageBegin()
-
- # unknown method
- if name not in self._processMap:
- iprot.skip(Pyload.TType.STRUCT)
- iprot.readMessageEnd()
- x = Pyload.TApplicationException(Pyload.TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % name)
- oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
-
- # not logged in
- elif not authenticated and not name == "login":
- iprot.skip(Pyload.TType.STRUCT)
- iprot.readMessageEnd()
- # 20 - Not logged in (in situ declared error code)
- x = Pyload.TApplicationException(20, 'Not logged in')
- oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
-
- elif not authenticated and name == "login":
- args = Pyload.login_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = Pyload.login_result()
- # api login
- self.authenticated[trans] = self._handler.checkAuth(args.username, args.password, trans.remoteaddr[0])
-
- result.success = True if self.authenticated[trans] else False
- oprot.writeMessageBegin("login", Pyload.TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- elif self._handler.isAuthorized(name, authenticated):
- self._processMap[name](self, seqid, iprot, oprot)
-
- else:
- #no permission
- iprot.skip(Pyload.TType.STRUCT)
- iprot.readMessageEnd()
- # 21 - Not authorized
- x = Pyload.TApplicationException(21, 'Not authorized')
- oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
-
- return True
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/__init__.py b/module/remote/thriftbackend/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/remote/thriftbackend/__init__.py
+++ /dev/null
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/__init__.py b/module/remote/thriftbackend/thriftgen/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/remote/thriftbackend/thriftgen/__init__.py
+++ /dev/null
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/__init__.py b/module/web/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/module/web/__init__.py
+++ /dev/null
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/default/css/pathchooser.css b/module/web/media/default/css/pathchooser.css
deleted file mode 100644
index 894cc335e..000000000
--- a/module/web/media/default/css/pathchooser.css
+++ /dev/null
@@ -1,68 +0,0 @@
-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/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/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/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/servers/nginx_default.conf b/module/web/servers/nginx_default.conf
deleted file mode 100644
index b4ebd1e02..000000000
--- a/module/web/servers/nginx_default.conf
+++ /dev/null
@@ -1,87 +0,0 @@
-daemon off;
-pid %(path)/nginx.pid;
-worker_processes 2;
-
-error_log %(path)/error.log info;
-
-events {
- worker_connections 1024;
- use epoll;
-}
-
-http {
- include /etc/nginx/conf/mime.types;
- default_type application/octet-stream;
-
- %(ssl)
-
- log_format main
- '$remote_addr - $remote_user [$time_local] '
- '"$request" $status $bytes_sent '
- '"$http_referer" "$http_user_agent" '
- '"$gzip_ratio"';
-
- error_log %(path)/error.log info;
-
- client_header_timeout 10m;
- client_body_timeout 10m;
- send_timeout 10m;
-
- client_body_temp_path %(path)/client_body_temp;
- proxy_temp_path %(path)/proxy_temp;
- fastcgi_temp_path %(path)/fastcgi_temp;
-
-
- connection_pool_size 256;
- client_header_buffer_size 1k;
- large_client_header_buffers 4 2k;
- request_pool_size 4k;
-
- gzip on;
- gzip_min_length 1100;
- gzip_buffers 4 8k;
- gzip_types text/plain;
-
- output_buffers 1 32k;
- postpone_output 1460;
-
- sendfile on;
- tcp_nopush on;
- tcp_nodelay on;
-
- keepalive_timeout 75 20;
-
- ignore_invalid_headers on;
-
- server {
- listen %(port);
- server_name %(host);
- # site_media - folder in uri for static files
- location ^~ /media {
- root %(media)/..;
- }
- location ^~ /admin/media {
- root /usr/lib/python%(version)/site-packages/django/contrib;
- }
-location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
- access_log off;
- expires 30d;
-}
- location / {
- # host and port to fastcgi server
- fastcgi_pass 127.0.0.1:9295;
- fastcgi_param PATH_INFO $fastcgi_script_name;
- fastcgi_param REQUEST_METHOD $request_method;
- fastcgi_param QUERY_STRING $query_string;
- fastcgi_param CONTENT_TYPE $content_type;
- fastcgi_param CONTENT_LENGTH $content_length;
- fastcgi_param SERVER_NAME $server_name;
- fastcgi_param SERVER_PORT $server_port;
- fastcgi_param SERVER_PROTOCOL $server_protocol;
- fastcgi_pass_header Authorization;
- fastcgi_intercept_errors off;
- }
- access_log %(path)/access.log main;
- error_log %(path)/error.log;
- }
- }
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/settings_item.html b/module/web/templates/default/settings_item.html
deleted file mode 100644
index 813383343..000000000
--- a/module/web/templates/default/settings_item.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<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> \ 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
deleted file mode 100644
index ac9a6fa1a..000000000
--- a/pavement.py
+++ /dev/null
@@ -1,332 +0,0 @@
-# -*- coding: utf-8 -*-
-
-
-from paver.easy import *
-from paver.setuputils import setup
-from paver.doctools import cog
-
-import sys
-import re
-from urllib import urlretrieve
-from subprocess import call, Popen, PIPE
-from zipfile import ZipFile
-
-PROJECT_DIR = path(__file__).dirname()
-sys.path.append(PROJECT_DIR)
-
-options = environment.options
-path('pyload').mkdir()
-
-extradeps = []
-if sys.version_info <= (2, 5):
- extradeps += 'simplejson'
-
-setup(
- name="pyload",
- version="0.4.9",
- description='Fast, lightweight and full featured download manager.',
- long_description=open(PROJECT_DIR / "README").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_data=find_package_data(),
- #data_files=[],
- include_package_data=True,
- 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,
- extras_require={
- 'SSL': ["pyOpenSSL"],
- 'DLC': ['pycrypto'],
- 'lightweight webserver': ['bjoern'],
- 'RSS plugins': ['feedparser'],
- },
- #setup_requires=["setuptools_hg"],
- entry_points={
- 'console_scripts': [
- 'pyLoadCore = pyLoadCore:main',
- 'pyLoadCli = pyLoadCli:main'
- ]},
- zip_safe=False,
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Topic :: Internet :: WWW/HTTP",
- "Environment :: Console",
- "Environment :: Web Environment",
- "Intended Audience :: End Users/Desktop",
- "License :: OSI Approved :: GNU General Public License (GPL)",
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 2"
- ]
-)
-
-options(
- sphinx=Bunch(
- builddir="_build",
- sourcedir=""
- ),
- get_source=Bunch(
- src="https://bitbucket.org/spoob/pyload/get/tip.zip",
- rev=None,
- clean=False
- ),
- thrift=Bunch(
- path="../thrift/trunk/compiler/cpp/thrift",
- gen=""
- ),
- virtualenv=Bunch(
- dir="env",
- python="python2",
- virtual="virtualenv2",
- ),
- cog=Bunch(
- pattern="*.py",
- )
-)
-
-# xgettext args
-xargs = ["--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()
- call_task('paver.doctools.html')
-
-
-@task
-@cmdopts([
- ('src=', 's', 'Url to source'),
- ('rev=', 'r', "HG revision"),
- ("clean", 'c', 'Delete old source folder')
-])
-def get_source(options):
- """ Downloads pyload source from bitbucket tip or given rev"""
- if options.rev: options.url = "https://bitbucket.org/spoob/pyload/get/%s.zip" % options.rev
-
- pyload = path("pyload")
-
- if len(pyload.listdir()) and not options.clean:
- return
- elif pyload.exists():
- pyload.rmtree()
-
- urlretrieve(options.src, "pyload_src.zip")
- zip = ZipFile("pyload_src.zip")
- zip.extractall()
- path("pyload_src.zip").remove()
-
- folder = [x for x in path(".").dirs() if x.name.startswith("spoob-pyload-")][0]
- folder.move(pyload)
-
- change_mode(pyload, 0644)
- change_mode(pyload, 0755, folder=True)
-
- for file in pyload.files():
- if file.name.endswith(".py"):
- file.chmod(0755)
-
- (pyload / ".hgtags").remove()
- (pyload / ".hgignore").remove()
- #(pyload / "docs").rmtree()
-
- f = open(pyload / "__init__.py", "wb")
- f.close()
-
- #options.setup.packages = find_packages()
- #options.setup.package_data = find_package_data()
-
-
-@task
-@needs('clean', 'generate_setup', 'minilib', 'get_source', 'setuptools.command.sdist')
-def sdist():
- """ Build source code package with distutils """
-
-
-@task
-@cmdopts([
- ('path=', 'p', 'Thrift path'),
- ('gen=', 'g', "Extra --gen option")
-])
-def thrift(options):
- """ Generate Thrift stubs """
-
- print "add import for TApplicationException manually as long it is not fixed"
-
- outdir = path("module") / "remote" / "thriftbackend"
- (outdir / "gen-py").rmtree()
-
- cmd = [options.thrift.path, "-strict", "-o", outdir, "--gen", "py:slots,dynamic", outdir / "pyload.thrift"]
-
- if options.gen:
- cmd.insert(len(cmd) - 1, "--gen")
- cmd.insert(len(cmd) - 1, options.gen)
-
- print "running", cmd
-
- p = Popen(cmd)
- p.communicate()
-
- (outdir / "thriftgen").rmtree()
- (outdir / "gen-py").move(outdir / "thriftgen")
-
- #create light ttypes
- from module.remote.socketbackend.create_ttypes import main
- main()
-
-@task
-def compile_js():
- """ Compile .coffee files to javascript"""
-
- root = path("module") / "web" / "media" / "js"
- for f in root.glob("*.coffee"):
- print "generate", f
- coffee = Popen(["coffee", "-cbs"], stdin=open(f, "rb"), stdout=PIPE)
- yui = Popen(["yuicompressor", "--type", "js"], stdin=coffee.stdout, stdout=PIPE)
- coffee.stdout.close()
- content = yui.communicate()[0]
- with open(root / f.name.replace(".coffee", ".js"), "wb") as js:
- js.write("{% autoescape true %}\n")
- js.write(content)
- js.write("\n{% endautoescape %}")
-
-
-@task
-def generate_locale():
- """ Generates localisation files """
-
- EXCLUDE = ["BeautifulSoup.py", "module/gui", "module/cli", "web/locale", "web/ajax", "web/cnl", "web/pyload",
- "setup.py"]
- makepot("core", path("module"), EXCLUDE, "./pyLoadCore.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")
-
- EXCLUDE = ["ServerThread.py", "web/media/default"]
-
- # strings from js files
- strings = set()
-
- for fi in path("module/web").walkfiles():
- if not fi.name.endswith(".js") and not fi.endswith(".coffee"): continue
- with open(fi, "rb") as c:
- content = c.read()
-
- strings.update(re.findall(r"_\s*\(\s*\"([^\"]+)", content))
- strings.update(re.findall(r"_\s*\(\s*\'([^\']+)", content))
-
- trans = path("module") / "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"])
-
- trans.remove()
-
- path("includes.txt").remove()
-
- print "Locale generated"
-
-
-@task
-def tests():
- call(["nosetests2"])
-
-@task
-def virtualenv(options):
- """Setup virtual environment"""
- if path(options.dir).exists():
- return
-
- call([options.virtual, "--no-site-packages", "--python", options.python, options.dir])
- print "$ source %s/bin/activate" % options.dir
-
-
-@task
-def clean_env():
- """Deletes the virtual environment"""
- env = path(options.virtualenv.dir)
- if env.exists():
- env.rmtree()
-
-
-@task
-@needs('generate_setup', 'minilib', 'get_source', 'virtualenv')
-def env_install():
- """Install pyLoad into the virtualenv"""
- venv = options.virtualenv
- call([path(venv.dir) / "bin" / "easy_install", "."])
-
-
-@task
-def clean():
- """Cleans build directories"""
- path("build").rmtree()
- path("dist").rmtree()
-
-
-#helper functions
-
-def walk_trans(path, EXCLUDE, endings=[".py"]):
- result = ""
-
- for f in path.walkfiles():
- if [True for x in EXCLUDE if x in f.dirname().relpath()]: continue
- if f.name in EXCLUDE: continue
-
- for e in endings:
- if f.name.endswith(e):
- result += "./%s\n" % f.relpath()
- break
-
- return result
-
-
-def makepot(domain, p, excludes=[], includes="", endings=[".py"], xxargs=[]):
- print "Generate %s.pot" % domain
-
- f = open("includes.txt", "wb")
- if includes:
- f.write(includes)
-
- if p:
- f.write(walk_trans(path(p), excludes, endings))
-
- f.close()
-
- call(["xgettext", "--files-from=includes.txt", "--default-domain=%s" % domain] + xargs + xxargs)
-
- # replace charset und move file
- with open("%s.po" % domain, "rb") as f:
- content = f.read()
-
- path("%s.po" % domain).remove()
- content = content.replace("charset=CHARSET", "charset=UTF-8")
-
- with open("locale/%s.pot" % domain, "wb") as f:
- f.write(content)
-
-
-def change_owner(dir, uid, gid):
- for p in dir.walk():
- p.chown(uid, gid)
-
-
-def change_mode(dir, mode, folder=False):
- for p in dir.walk():
- if folder and p.isdir():
- p.chmod(mode)
- elif p.isfile() and not folder:
- p.chmod(mode)
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.py b/pyload.py
new file mode 100755
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/Api/__init__.py b/pyload/Api/__init__.py
new file mode 100644
index 000000000..44bae48bc
--- /dev/null
+++ b/pyload/Api/__init__.py
@@ -0,0 +1,1051 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+import base64
+import os
+import re
+import time
+import urlparse
+
+from pyload.Datatype import PyFile
+from pyload.utils.packagetools import parseNames
+from pyload.network.RequestFactory import getURL
+from pyload.remote import activated
+from pyload.utils import compare_time, freeSpace, safe_filename
+
+if activated:
+ try:
+ from thrift.protocol import TBase
+ from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import *
+ from pyload.remote.thriftbackend.thriftgen.pyload.Pyload import Iface
+
+ BaseObject = TBase
+
+ except ImportError:
+ from pyload.Api.types import *
+
+ print "Thrift not imported"
+
+else:
+ from pyload.Api.types 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(object):
+ 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(object):
+ 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):
+ fdata = 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 fdata
+
+
+ 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)
+
+
+ @permission(PERMS.SETTINGS)
+ def setConfigValue(self, category, option, value, section="core"):
+ """Set new config value.
+
+ :param section:
+ :param option:
+ :param value: new config value
+ :param section: 'plugin' or 'core
+ """
+ self.core.addonManager.dispatchEvent("config-changed", 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.get("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.get("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.get("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 = os.path.join(self.core.config.get("log", "log_folder"), 'log.txt')
+ try:
+ with open(filename, "r") as fh:
+ lines = fh.readlines()
+ if offset >= len(lines):
+ return []
+ return lines[offset:]
+ except Exception:
+ 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.get("downloadTime", "start").split(":")
+ end = self.core.config.get("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.get("reconnect", "startTime").split(":")
+ end = self.core.config.get("reconnect", "endTime").split(":")
+ return compare_time(start, end) and self.core.config.get("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.get("general", "folder_per_package"):
+ folder = urlparse.urlparse(name).path.split("/")[-1]
+ else:
+ folder = ""
+
+ folder = safe_filename(folder)
+
+ 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.decode('utf-8'), "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, plugintype, pluginname in data:
+ try:
+ plugins[plugintype][pluginname].append(url)
+ except Exception:
+ plugins[plugintype][pluginname] = [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, (plugintype, pluginname), "unknown", 3, 0))) for url, plugintype, 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
+ """
+ with open(os.path.join(self.core.config.get("general", "download_folder"), "tmp_" + container), "wb") as th:
+ th.write(str(data))
+ 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
+ """
+ return parseNames((x, x) for x in links)
+
+
+ @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)
+ return 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()])
+
+
+ @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)
+ return PackageData(data['id'], data['name'], data['folder'], data['site'], data['password'],
+ data['queue'], data['order'],
+ fids=[int(x) for x in data['links']])
+
+
+ @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)
+ return self._convertPyFile(info.values()[0])
+
+
+ @permission(PERMS.DELETE)
+ def deleteFiles(self, fids):
+ """Deletes several file entries from pyload.
+
+ :param fids: list of file ids
+ """
+ for fid in fids:
+ self.core.files.deleteLink(int(fid))
+ self.core.files.save()
+
+
+ @permission(PERMS.DELETE)
+ def deletePackages(self, pids):
+ """Deletes packages and containing links.
+
+ :param pids: list of package ids
+ """
+ for pid in pids:
+ self.core.files.deletePackage(int(pid))
+ 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 in (0, 1):
+ 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
+ """
+ with open(os.path.join(self.core.config.get("general", "download_folder"), "tmp_" + filename), "wb") as th:
+ th.write(str(data))
+ 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
+ """
+ package = self.core.files.getPackage(pid)
+ if not package:
+ raise PackageDoesNotExists(pid)
+ for key, value in data.iteritems():
+ if key == "id":
+ continue
+ setattr(package, key, value)
+ package.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.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.time()
+ task = self.core.captchaManager.getTask()
+ if task:
+ task.setWatingForUser(exclusive=exclusive)
+ data, type, result = task.getCaptcha()
+ ctask = CaptchaTask(int(task.id), base64.standard_b64encode(data), type, result)
+ return ctask
+ 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.time()
+ task = self.core.captchaManager.getTaskByID(tid)
+ return task.getStatus() if task 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.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)
+ new_events = []
+
+
+ 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])
+ new_events.append(event)
+ return new_events
+
+
+ @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)
+ for group in accs.values():
+ accounts = [AccountInfo(acc['validuntil'], acc['login'], acc['options'], acc['valid'],
+ acc['trafficleft'], acc['maxtraffic'], acc['premium'], acc['type'])
+ for acc in group]
+ return accounts or list()
+
+
+ @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 bool(self.checkAuth(username, password, remoteip))
+
+
+ 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.get("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"""
+ return dict((user, UserData(user, data['email'], data['role'], data['permission'], data['template'])) for user, data
+ in self.core.db.getAllUserData().iteritems())
+
+
+ @permission(PERMS.STATUS)
+ def getServices(self):
+ """ A dict of available services, these can be defined by addon plugins.
+
+ :return: dict with this style: {"plugin": {"method": "description"}}
+ """
+ return dict((plugin, funcs) for plugin, funcs in self.core.addonManager.methods.iteritems())
+
+
+ @permission(PERMS.STATUS)
+ def hasService(self, plugin, func):
+ """Checks wether a service is available.
+
+ :param plugin:
+ :param func:
+ :return: bool
+ """
+ cont = self.core.addonManager.methods
+ return plugin in cont and func in cont[plugin]
+
+
+ @permission(PERMS.STATUS)
+ def call(self, info):
+ """Calls a service (a method in addon 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.addonManager.callRPC(plugin, func, args, parse)
+ except Exception, e:
+ raise ServiceException(e.message)
+
+
+ @permission(PERMS.STATUS)
+ def getAllInfo(self):
+ """Returns all information stored by addon plugins. Values are always strings
+
+ :return: {"plugin": {"name": value}}
+ """
+ return self.core.addonManager.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.addonManager.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, perm, role):
+ self.core.db.setPermission(user, perm)
+ self.core.db.setRole(user, role)
diff --git a/pyload/Api/types.py b/pyload/Api/types.py
new file mode 100644
index 000000000..9381df3c7
--- /dev/null
+++ b/pyload/Api/types.py
@@ -0,0 +1,562 @@
+# -*- 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(object):
+ Collector = 0
+ Queue = 1
+
+
+class DownloadStatus(object):
+ 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(object):
+ File = 1
+ Package = 0
+
+
+class Input(object):
+ BOOL = 4
+ CHOICE = 6
+ CLICK = 5
+ LIST = 8
+ MULTIPLE = 7
+ NONE = 0
+ PASSWORD = 3
+ TABLE = 9
+ TEXT = 1
+ TEXTBOX = 2
+
+
+class Output(object):
+ 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, 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(object):
+
+ 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/pyload/Core.py b/pyload/Core.py
new file mode 100755
index 000000000..1ec6ee5ed
--- /dev/null
+++ b/pyload/Core.py
@@ -0,0 +1,677 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay, sebnapi, spoob, vuolter
+# @version: v0.4.10
+
+from __future__ import with_statement
+
+import __builtin__
+import codecs
+import getopt
+import imp
+import logging
+import os
+import signal
+import subprocess
+import sys
+import time
+import traceback
+
+import pyload
+import pyload.utils.pylgettext as gettext
+
+from pyload import remote
+from pyload.Database import DatabaseBackend, FileHandler
+from pyload.config.Parser import ConfigParser
+from pyload.manager.Account import AccountManager
+from pyload.manager.Captcha import CaptchaManager
+from pyload.manager.Event import PullManager
+from pyload.manager.Plugin import PluginManager
+from pyload.manager.Remote import RemoteManager
+from pyload.manager.Scheduler import Scheduler
+from pyload.Thread.Server import WebServer
+from pyload.network.JsEngine import JsEngine
+from pyload.network.RequestFactory import RequestFactory
+from pyload.utils import freeSpace, formatSize
+
+
+# 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.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", pyload.__version__
+ sys.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()
+ sys.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(self.config)
+ s.set_user()
+ sys.exit()
+ elif option in ("-s", "--setup"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ s.start()
+ sys.exit()
+ elif option == "--changedir":
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(self.config)
+ s.conf_path(True)
+ sys.exit()
+ elif option in ("-q", "--quit"):
+ self.quitInstance()
+ sys.exit()
+ elif option == "--status":
+ pid = self.isAlreadyRunning()
+ if self.isAlreadyRunning():
+ print pid
+ sys.exit(0)
+ else:
+ print "false"
+ sys.exit(1)
+ elif option == "--clean":
+ self.cleanTree()
+ sys.exit()
+ elif option == "--no-remote":
+ self.remote = False
+
+ except getopt.GetoptError:
+ print 'Unknown Argument(s) "%s"' % " ".join(argv[1:])
+ self.print_help()
+ sys.exit()
+
+
+ def print_help(self):
+ print
+ print "pyLoad v%s 2008-2015 the pyLoad Team" % pyload.__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"))
+ os._exit(1)
+
+
+ def writePidFile(self):
+ self.deletePidFile()
+ pid = os.getpid()
+ with open(self.pidfile, "wb") as f:
+ f.write(str(pid))
+
+
+ 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):
+ with open(self.pidfile, "rb") as f:
+ pid = f.read().strip()
+ 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 Exception:
+ 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.time()
+ print "waiting for pyLoad to quit"
+
+ whileself.pidfile) and t + 10 > time.time():
+ time.sleep(0.25)
+
+ if not os.path.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 Exception:
+ print "Error quitting pyLoad"
+
+
+ def cleanTree(self):
+ for path, dirs, files in os.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 os.path.join(path, f)
+ reshutil.move(os.path.join(path, f))
+
+
+ def start(self, rpc=True, web=True):
+ """ starts the fun :D """
+
+ self.version = pyload.__version__
+
+ if not os.path.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(self.config)
+ res = False
+ try:
+ res = s.start()
+ except SystemExit:
+ pass
+ except KeyboardInterrupt:
+ print "\nSetup interrupted"
+ except Exception:
+ res = False
+ traceback.print_exc()
+ print "Setup failed"
+ if not res:
+ reshutil.move("pyload.conf")
+
+ sys.exit()
+
+ try: signal.signal(signal.SIGQUIT, self.quit)
+ except Exception:
+ pass
+
+ self.config = ConfigParser()
+
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("pyLoad", self.path("locale"),
+ languages=[self.config.get("general", "language"), "en"], fallback=True)
+ translation.install(True)
+
+ self.debug = self.doDebug or self.config.get("general", "debug_mode")
+ self.remote &= self.config.get("remote", "activated")
+
+ pid = self.isAlreadyRunning()
+ if pid:
+ print _("pyLoad already running with pid %s") % pid
+ sys.exit()
+
+ if os.name != "nt" and self.config.get("general", "renice"):
+ os.system("renice %d %d" % (self.config.get("general", "renice"), os.getpid()))
+
+ if self.config.get("permission", "change_group"):
+ if os.name != "nt":
+ try:
+ from grp import getgrnam
+
+ group = getgrnam(self.config.get("permission", "group"))
+ os.setgid(group[2])
+ except Exception, e:
+ print _("Failed changing group: %s") % e
+
+ if self.config.get("permission", "change_user"):
+ if os.name != "nt":
+ try:
+ from pwd import getpwnam
+
+ user = getpwnam(self.config.get("permission", "user"))
+ os.setuid(user[2])
+ except Exception, e:
+ print _("Failed changing user: %s") % e
+
+ self.check_file(self.config.get("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" % pyload.__version__)
+ self.log.info(_("Using home directory: %s") % os.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.get("general", "download_folder"), _("folder for downloads"), True)
+
+ if self.config.get("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.Api import Api
+ from pyload.manager.Addon import AddonManager
+ from pyload.manager.Thread import ThreadManager
+
+ if pyload.Api.activated != self.remote:
+ self.log.warning("Import error: API remote status not correct.")
+
+ self.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.addonManager = AddonManager(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.get("general", "download_folder"))
+
+ self.log.info(_("Free space: %s") % formatSize(spaceLeft))
+
+ self.config.save() #: save so config files gets filled
+
+ link_file = os.path.join(pypath, "links.txt")
+
+ if os.path.exists(link_file):
+ with open(link_file, "rb") as f:
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+
+ link_file = "links.txt"
+ if os.path.exists(link_file):
+ with open(link_file, "rb") as f:
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+
+ # 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.addonManager.coreReady()
+
+ self.log.info(_("pyLoad is up and running"))
+
+ locals().clear()
+
+ while True:
+ time.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()
+ os._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.get("webui", "activated"):
+ self.webserver = WebServer(self)
+ self.webserver.start()
+
+
+ def init_logger(self, level):
+ self.log = logging.logging.getLogger("log")
+ self.log.setLevel(level)
+
+ date_fmt = "%Y-%m-%d %H:%M:%S"
+ fh_fmt = "%(asctime)s %(levelname)-8s %(message)s"
+
+ fh_frm = logging.Formatter(fh_fmt, date_fmt) #: file handler formatter
+ console_frm = fh_frm #: console formatter did not use colors as default
+
+ # Console formatter with colors
+ if self.config.get("log", "color_console"):
+ import colorlog
+
+ if self.config.get("log", "console_mode") == "label":
+ c_fmt = "%(asctime)s %(log_color)s%(bold)s%(white)s %(levelname)-8s%(reset)s %(message)s"
+ clr = {
+ 'DEBUG' : "bg_cyan" ,
+ 'INFO' : "bg_green" ,
+ 'WARNING' : "bg_yellow",
+ 'ERROR' : "bg_red" ,
+ 'CRITICAL': "bg_purple",
+ }
+
+ else:
+ c_fmt = "%(log_color)s%(asctime)s %(levelname)-8s %(message)s"
+ clr = {
+ 'DEBUG' : "cyan" ,
+ 'WARNING' : "yellow",
+ 'ERROR' : "red" ,
+ 'CRITICAL': "purple",
+ }
+
+ console_frm = colorlog.ColoredFormatter(c_fmt, date_fmt, clr)
+
+ # Set console formatter
+ console = logging.StreamHandler(sys.stdout)
+ console.setFormatter(console_frm)
+ self.log.addHandler(console)
+
+ log_folder = self.config.get("log", "log_folder")
+ if not os.path.exists(log_folder):
+ os.makedirs(log_folder, 0700)
+
+ # Set file handler formatter
+ if self.config.get("log", "file_log"):
+ if self.config.get("log", "log_rotate"):
+ file_handler = logging.handlers.RotatingFileHandler(os.path.join(log_folder, 'log.txt'),
+ maxBytes=self.config.get("log", "log_size") * 1024,
+ backupCount=int(self.config.get("log", "log_count")),
+ encoding="utf8")
+ else:
+ file_handler = logging.FileHandler(os.path.join(log_folder, 'log.txt'), encoding="utf8")
+
+ file_handler.setFormatter(fh_frm)
+ self.log.addHandler(file_handler)
+
+
+ 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:
+ imp.find_module(check_name)
+ else:
+ pipe = subprocess.PIPE
+ subprocess.Popen(check_name, stdout=pipe, stderr=pipe)
+
+ return True
+ except Exception:
+ if essential:
+ self.log.info(_("Install %s") % legend)
+ sys.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 os.path.exists(tmp_name):
+ file_exists = False
+ if empty:
+ try:
+ if folder:
+ tmp_name = tmp_name.replace("/", sep)
+ os.makedirs(tmp_name)
+ else:
+ open(tmp_name, "w")
+ except Exception:
+ 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:
+ sys.exit()
+
+
+ def isClientConnected(self):
+ return (self.lastClientConnected + 30) > time.time()
+
+
+ def restart(self):
+ self.shutdown()
+ os.chdir(owd)
+ # close some open fds
+ for i in xrange(3, 50):
+ try:
+ os.close(i)
+ except Exception:
+ pass
+
+ os.execl(executable, executable, *sys.argv)
+ os._exit(0)
+
+
+ def shutdown(self):
+ self.log.info(_("shutting down..."))
+ try:
+ if self.config.get("webui", "activated") and hasattr(self, "webserver"):
+ self.webserver.quit()
+
+ for thread in list(self.threadManager.threads):
+ thread.put("quit")
+ pyfiles = self.files.cache.values()
+
+ for pyfile in pyfiles:
+ pyfile.abortDownload()
+
+ self.addonManager.coreExiting()
+
+ except Exception:
+ if self.debug:
+ traceback.print_exc()
+ self.log.info(_("error while shutting down"))
+
+ finally:
+ self.files.syncSave()
+ self.shuttedDown = True
+
+ self.deletePidFile()
+
+
+ def os.path(self, *args):
+ return os.path.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 xrange(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()
+ os._exit(1)
+
+# And so it begins...
+if __name__ == "__main__":
+ main()
diff --git a/pyload/Database/Backend.py b/pyload/Database/Backend.py
new file mode 100644
index 000000000..622880e65
--- /dev/null
+++ b/pyload/Database/Backend.py
@@ -0,0 +1,328 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from __future__ import with_statement
+
+try:
+ from pysqlite2 import dbapi2 as sqlite3
+except Exception:
+ import sqlite3
+
+import Queue
+import shutil
+import threading
+import traceback
+
+from pyload.utils import chmod
+
+
+DB_VERSION = 4
+
+
+class style(object):
+ 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(object):
+
+ def __init__(self, f, *args, **kwargs):
+ self.done = threading.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):
+ import os
+
+ frame = self.frame.f_back
+ output = ""
+ for _i in xrange(5):
+ output += "\t%s:%s, %s\n" % (os.path.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:
+ traceback.print_exc()
+ try:
+ print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e
+ except Exception:
+ pass
+
+ self.exception = e
+ finally:
+ self.done.set()
+
+
+ def wait(self):
+ self.done.wait()
+
+
+class DatabaseBackend(threading.Thread):
+ subs = []
+
+
+ def __init__(self, core):
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+ self.core = core
+
+ self.jobs = Queue.Queue()
+
+ self.setuplock = threading.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")
+ os.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 os.path.exists("files.version"):
+ with open("files.version", "wb") as f:
+ f.write(str(DB_VERSION))
+ return
+
+ with open("files.version", "rb") as f:
+ v = int(f.read().strip() or 0)
+
+ if v < DB_VERSION:
+ if v < 2:
+ try:
+ self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version."))
+ except Exception:
+ print "Filedatabase was deleted due to incompatible version."
+ reshutil.move("files.version")
+ shutil.move("files.db", "files.backup.db")
+
+ with open("files.version", "wb") as f:
+ f.write(str(DB_VERSION))
+
+ return v
+
+
+ def _convertDB(self, v):
+ try:
+ getattr(self, "_convertV%i" % v)()
+ except Exception:
+ try:
+ self.core.log.error(_("Filedatabase could NOT be converted."))
+ except Exception:
+ 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 Exception:
+ 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 Exception:
+ 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]
+ fid = int(fid) if fid else 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]
+ pid = int(pid) if pid else 0
+ self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages"))
+
+ self.c.execute('VACUUM')
+
+
+ def _migrateUser(self):
+ if os.path.exists("pyload.db"):
+ try:
+ self.core.log.info(_("Converting old Django DB"))
+ except Exception:
+ 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)
+ shutil.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/File.py b/pyload/Database/File.py
new file mode 100644
index 000000000..f28535ffa
--- /dev/null
+++ b/pyload/Database/File.py
@@ -0,0 +1,985 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+import threading
+
+from pyload.Datatype import PyFile, PyPackage
+from pyload.Database import DatabaseBackend, style
+from pyload.manager.Event import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent
+from pyload.utils import formatSize, lock
+
+try:
+ from pysqlite2 import dbapi2 as sqlite3
+except Exception:
+ import sqlite3
+
+
+class FileHandler(object):
+ """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 = threading.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.addonManager.dispatchEvent("links-added", 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.addonManager.dispatchEvent("package-deleted", 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.addonManager.dispatchEvent("all_downloads-finished")
+ 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.addonManager.dispatchEvent("all_downloads-processed")
+ 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 position <= pack.order < p.order:
+ pack.order += 1
+ pack.notifyChange()
+ elif p.order < position:
+ if position >= 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 position <= pyfile.order < f['order']:
+ pyfile.order += 1
+ pyfile.notifyChange()
+ elif f['order'] < position:
+ if position >= 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 AddonManager """
+
+ 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.addonManager.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 = [id for id in old_packs.iterkeys() if id not in new_packs]
+ for id_deleted in deleted:
+ self.deletePackage(int(id_deleted))
+
+ return deleted
+
+
+ @lock
+ @change
+ def restartFailed(self):
+ """ restart all failed links """
+ self.db.restartFailed()
+
+
+class FileMethods(object):
+
+
+ @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, ".".join(plugintype, pluginname), 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 xrange(len(links))]
+ links = [(x[0], x[0], ".".join((x[1], x[2])), 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': tuple(r[6].split('.')),
+ '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': tuple(r[6].split('.')),
+ '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': tuple(r[6].split('.')),
+ '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, str(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
+ r = list(r)
+ r[5] = tuple(r[5].split('.'))
+ 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 = "('CCF', 'DLC', 'LinkList', 'RSDF', 'TXT')" #: 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/Storage.py b/pyload/Database/Storage.py
new file mode 100644
index 000000000..a19f67606
--- /dev/null
+++ b/pyload/Database/Storage.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+from pyload.Database import style
+from pyload.Database import DatabaseBackend
+
+
+class StorageMethods(object):
+
+ @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,))
+ return {row[0]: row[1] for row in db.c}
+
+
+ @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/User.py b/pyload/Database/User.py
new file mode 100644
index 000000000..a91e16cc6
--- /dev/null
+++ b/pyload/Database/User.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+import hashlib
+import random
+
+from pyload.Database import DatabaseBackend, style
+
+
+class UserMethods(object):
+
+
+ @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 = hashlib.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 xrange(0, 5)])
+ h = hashlib.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 = hashlib.sha1(salt + oldpw)
+ if h.hexdigest() == pw:
+ salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for _i in xrange(0, 5)])
+ h = hashlib.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')
+ return [row[0] for row in db.c]
+
+
+ @style.queue
+ def getAllUserData(db):
+ db.c.execute("SELECT name, permission, role, template, email FROM users")
+ return {r[0]: {"permission": r[1], "role": r[2], "template": r[3], "email": r[4]} for r in db.c}
+
+
+ @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..b4200a698
--- /dev/null
+++ b/pyload/Database/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+
+from pyload.Database.Backend import DatabaseBackend, style
+from pyload.Database.File import FileHandler
+from pyload.Database.Storage import StorageMethods
+from pyload.Database.User import UserMethods
diff --git a/pyload/Datatype/File.py b/pyload/Datatype/File.py
new file mode 100644
index 000000000..cb6989d98
--- /dev/null
+++ b/pyload/Datatype/File.py
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+import time
+import threading
+
+from pyload.manager.Event import UpdateEvent
+from pyload.utils import formatSize, lock
+
+
+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", "plugintype", "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, plugin, package, order):
+ self.m = manager
+
+ self.id = int(id)
+ self.url = url
+ self.name = name
+ self.size = size
+ self.status = status
+ self.plugintype, self.pluginname = plugin
+ self.packageid = package #: should not be used, use package() instead
+ self.error = error
+ self.order = order
+ # database information ends here
+
+ self.lock = threading.RLock()
+
+ self.plugin = None
+ # self.download = None
+
+ self.waitUntil = 0 #: time.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.plugintype, self.pluginname)
+ self.pluginclass = getattr(self.pluginmodule, self.m.core.pluginManager.getPluginName(self.plugintype, 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()
+ time.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.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 Exception:
+ return 0
+
+
+ def getETA(self):
+ """ gets established time of arrival"""
+ try:
+ return self.getBytesLeft() / self.getSpeed()
+ except Exception:
+ return 0
+
+
+ def getBytesLeft(self):
+ """ gets bytes left """
+ try:
+ return self.getSize() - self.plugin.req.arrived
+ except Exception:
+ return 0
+
+
+ def getPercent(self):
+ """ get % of download """
+ if self.status == 12:
+ try:
+ return self.plugin.req.percent
+ except Exception:
+ 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 Exception:
+ 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/Datatype/Package.py b/pyload/Datatype/Package.py
new file mode 100644
index 000000000..5ba42f596
--- /dev/null
+++ b/pyload/Datatype/Package.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+from pyload.manager.Event import UpdateEvent
+from pyload.utils import safe_filename
+
+
+class PyPackage(object):
+ """
+ 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/pyload/Datatype/__init__.py b/pyload/Datatype/__init__.py
new file mode 100644
index 000000000..29f0d40aa
--- /dev/null
+++ b/pyload/Datatype/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from pyload.Datatype.File import PyFile
+from pyload.Datatype.Package import PyPackage
diff --git a/pyload/Thread/Addon.py b/pyload/Thread/Addon.py
new file mode 100644
index 000000000..24a2fc26a
--- /dev/null
+++ b/pyload/Thread/Addon.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import Queue
+import copy
+import os
+import sys
+import time
+import traceback
+
+from pyload.Thread.Plugin import PluginThread
+
+
+class AddonThread(PluginThread):
+ """thread for addons"""
+
+ 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.copy(self.active)
+ for x in local:
+ self.finishFile(x)
+
+ self.m.localThreads.remove(self)
diff --git a/pyload/Thread/Decrypter.py b/pyload/Thread/Decrypter.py
new file mode 100644
index 000000000..5a8cff7c1
--- /dev/null
+++ b/pyload/Thread/Decrypter.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import Queue
+import os
+import sys
+import time
+import traceback
+
+from pyload.Thread.Plugin import PluginThread
+from pyload.plugin.Plugin import Abort, Fail, Retry
+
+
+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.core.log.info(_("Decrypting starts: %s") % pyfile.name)
+ pyfile.error = ""
+ pyfile.plugin.preprocessing(self)
+
+ except NotImplementedError:
+ self.m.core.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname)
+ return
+
+ except Fail, e:
+ msg = e.args[0]
+
+ if msg == "offline":
+ pyfile.setStatus("offline")
+ self.m.core.log.warning(
+ _("Download is offline: %s") % pyfile.name)
+ else:
+ pyfile.setStatus("failed")
+ self.m.core.log.error(
+ _("Decrypting failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
+ pyfile.error = msg
+
+ if self.m.core.debug:
+ traceback.print_exc()
+ return
+
+ except Abort:
+ self.m.core.log.info(_("Download aborted: %s") % pyfile.name)
+ pyfile.setStatus("aborted")
+
+ if self.m.core.debug:
+ traceback.print_exc()
+ return
+
+ except Retry:
+ self.m.core.log.info(_("Retrying %s") % pyfile.name)
+ retry = True
+ return self.run()
+
+ except Exception, e:
+ pyfile.setStatus("failed")
+ self.m.core.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {
+ "name": pyfile.name, "msg": str(e)})
+ pyfile.error = str(e)
+
+ if self.m.core.debug:
+ traceback.print_exc()
+ self.writeDebugReport(pyfile)
+
+ return
+
+ finally:
+ if not retry:
+ pyfile.release()
+ self.active = False
+ self.m.core.files.save()
+ self.m.localThreads.remove(self)
+ sys.exc_clear()
+
+ if not retry:
+ pyfile.delete()
diff --git a/pyload/Thread/Download.py b/pyload/Thread/Download.py
new file mode 100644
index 000000000..49f6145ce
--- /dev/null
+++ b/pyload/Thread/Download.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import Queue
+import os
+import sys
+import time
+import traceback
+
+import pycurl
+
+from pyload.Thread.Plugin import PluginThread
+from pyload.plugin.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+
+
+class DownloadThread(PluginThread):
+ """thread for downloading files from 'real' hoster plugins"""
+
+ def __init__(self, manager):
+ """Constructor"""
+ PluginThread.__init__(self, manager)
+
+ self.queue = Queue.Queue() #: job queue
+ self.active = False
+
+ self.start()
+
+
+ #--------------------------------------------------------------------------
+
+ def run(self):
+ """run method"""
+ pyfile = None
+
+ while True:
+ del pyfile
+ self.active = False #: sets the thread inactive when it is ready to get next job
+ 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.core.log.info(_("Download starts: %s" % pyfile.name))
+
+ # start download
+ self.m.core.addonManager.downloadPreparing(pyfile)
+ pyfile.error = ""
+ pyfile.plugin.preprocessing(self)
+
+ self.m.core.log.info(_("Download finished: %s") % pyfile.name)
+ self.m.core.addonManager.downloadFinished(pyfile)
+ self.m.core.files.checkPackageFinished(pyfile)
+
+ except NotImplementedError:
+ self.m.core.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.core.log.info(_("Download aborted: %s") % pyfile.name)
+ except Exception:
+ pass
+
+ pyfile.setStatus("aborted")
+
+ if self.m.core.debug:
+ traceback.print_exc()
+
+ self.clean(pyfile)
+ continue
+
+ except Reconnect:
+ self.queue.put(pyfile)
+ # pyfile.req.clearCookies()
+
+ while self.m.reconnecting.isSet():
+ time.sleep(0.5)
+
+ continue
+
+ except Retry, e:
+ reason = e.args[0]
+ self.m.core.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.core.log.warning(_("Download is offline: %s") % pyfile.name)
+ elif msg == "temp. offline":
+ pyfile.setStatus("temp. offline")
+ self.m.core.log.warning(_("Download is temporary offline: %s") % pyfile.name)
+ else:
+ pyfile.setStatus("failed")
+ self.m.core.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
+ pyfile.error = msg
+
+ if self.m.core.debug:
+ traceback.print_exc()
+
+ self.m.core.addonManager.downloadFailed(pyfile)
+ self.clean(pyfile)
+ continue
+
+ except pycurl.error, e:
+ if len(e.args) == 2:
+ code, msg = e.args
+ else:
+ code = 0
+ msg = e.args
+
+ self.m.core.log.debug("pycurl exception %s: %s" % (code, msg))
+
+ if code in (7, 18, 28, 52, 56):
+ self.m.core.log.warning(_("Couldn't connect to host or connection reset, waiting 1 minute and retry."))
+ wait = time.time() + 60
+
+ pyfile.waitUntil = wait
+ pyfile.setStatus("waiting")
+ while time.time() < wait:
+ time.sleep(1)
+ if pyfile.abort:
+ break
+
+ if pyfile.abort:
+ self.m.core.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.core.log.error("pycurl error %s: %s" % (code, msg))
+ if self.m.core.debug:
+ traceback.print_exc()
+ self.writeDebugReport(pyfile)
+
+ self.m.core.addonManager.downloadFailed(pyfile)
+
+ self.clean(pyfile)
+ continue
+
+ except SkipDownload, e:
+ pyfile.setStatus("skipped")
+
+ self.m.core.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.core.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)})
+ pyfile.error = str(e)
+
+ if self.m.core.debug:
+ traceback.print_exc()
+ self.writeDebugReport(pyfile)
+
+ self.m.core.addonManager.downloadFailed(pyfile)
+ self.clean(pyfile)
+ continue
+
+ finally:
+ self.m.core.files.save()
+ pyfile.checkIfProcessed()
+ sys.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")
diff --git a/pyload/Thread/Info.py b/pyload/Thread/Info.py
new file mode 100644
index 000000000..cf76fda4b
--- /dev/null
+++ b/pyload/Thread/Info.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import Queue
+import os
+import sys
+import time
+import traceback
+
+from pyload.Api import OnlineStatus
+from pyload.Datatype import PyFile
+from pyload.Thread.Plugin import PluginThread
+
+
+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, plugintype, pluginname in self.data:
+ # filter out container plugins
+ if plugintype == 'container':
+ container.appen((pluginname, url))
+ else:
+ if (plugintype, pluginname) in plugins:
+ plugins[(plugintype, pluginname)].append(url)
+ else:
+ plugins[(plugintype, pluginname)] = [url]
+
+ # directly write to database
+ if self.pid > -1:
+ for (plugintype, pluginname), urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(plugintype, pluginname, True)
+ if hasattr(plugin, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateDB)
+ self.m.core.files.save()
+
+ elif self.add:
+ for (plugintype, pluginname), urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(plugintype, 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 Exception:
+ traceback.print_exc()
+ self.m.log.error("Could not decrypt container.")
+ data = []
+
+ for url, plugintype, pluginname in data:
+ try:
+ plugins[plugintype][pluginname].append(url)
+ except Exception:
+ plugins[plugintype][pluginname] = [url]
+
+ self.m.infoResults[self.rid] = {}
+
+ for plugintype, pluginname, urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(plugintype, 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.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 # : why don't assign res dict directly?
+
+ 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:
+ traceback.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/Thread/Plugin.py b/pyload/Thread/Plugin.py
new file mode 100644
index 000000000..cba82946d
--- /dev/null
+++ b/pyload/Thread/Plugin.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+import Queue
+import os
+import sys
+import threading
+import time
+import traceback
+import pprint
+import types
+
+from pyload.Api import OnlineStatus
+from pyload.Datatype import PyFile
+from pyload.plugin.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import fs_join
+
+
+class PluginThread(threading.Thread):
+ """abstract base class for thread types"""
+
+ def __init__(self, manager):
+ """Constructor"""
+ threading.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, time.strftime("%d-%m-%Y_%H-%M-%S"))
+ dump = self.getDebugDump(pyfile)
+
+ try:
+ import zipfile
+
+ zip = zipfile.ZipFile(dump_name, "w")
+
+ for f in os.listdir(os.path.join("tmp", pyfile.pluginname)):
+ try:
+ # avoid encoding errors
+ zip.write(os.path.join("tmp", pyfile.pluginname, f), fs_join(pyfile.pluginname, f))
+ except Exception:
+ pass
+
+ info = zipfile.ZipInfo(fs_join(pyfile.pluginname, "debug_Report.txt"), time.gmtime())
+ info.external_attr = 0644 << 16L #: change permissions
+
+ zip.writestr(info, dump)
+ zip.close()
+
+ if not os.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")
+ with open(dump_name, "wb") as f:
+ f.write(dump)
+
+ 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, traceback.format_exc())
+
+ tb = sys.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 += pprint.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) != types.MethodType:
+ dump += "\t%20s = " % name
+ try:
+ dump += pprint.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) != types.MethodType:
+ dump += "\t%20s = " % name
+ try:
+ dump += pprint.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 += pprint.pformat(self.m.core.config.plugin[pyfile.pluginname]) + "\n"
+
+ return dump
+
+
+ def clean(self, pyfile):
+ """ set thread unactive and release pyfile """
+ self.active = True #: release pyfile but lets the thread active
+ pyfile.release()
diff --git a/pyload/Thread/Server.py b/pyload/Thread/Server.py
new file mode 100644
index 000000000..a26ffe6a1
--- /dev/null
+++ b/pyload/Thread/Server.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import logging
+import os
+import threading
+
+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.get("webui", "server")
+ self.https = pycore.config.get("webui", "https")
+ self.cert = pycore.config.get("ssl", "cert")
+ self.key = pycore.config.get("ssl", "key")
+ self.host = pycore.config.get("webui", "host")
+ self.port = pycore.config.get("webui", "port")
+
+ self.setDaemon(True)
+
+
+ def run(self):
+ import pyload.webui as webinterface
+ global webinterface
+
+ reset = False
+
+ if self.https and (not os.exists(self.cert) or not os.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 Exception:
+ 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 file to lib/Python/Lib or use setup.py install"))
+ log.warning(_("Of course you need to be familiar with linux and know how to compile software"))
+ self.server = "auto"
+ else:
+ self.core.log.info(_("Server set to threaded, due to known performance problems on windows."))
+ self.core.config.set("webui", "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_auto()
+
+
+ def start_auto(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_auto(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})
+ try:
+ webinterface.run_fcgi(host=self.host, port=self.port)
+
+ except ValueError: #@TODO: Fix https://github.com/pyload/pyload/issues/1145
+ pass
+
+
+ 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/pyload/Thread/__init__.py b/pyload/Thread/__init__.py
new file mode 100644
index 000000000..30eda15c2
--- /dev/null
+++ b/pyload/Thread/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+from pyload.Thread.Addon import AddonThread
+from pyload.Thread.Decrypter import DecrypterThread
+from pyload.Thread.Download import DownloadThread
+from pyload.Thread.Info import InfoThread
+from pyload.Thread.Plugin import PluginThread
diff --git a/pyload/__init__.py b/pyload/__init__.py
new file mode 100644
index 000000000..fe1e04437
--- /dev/null
+++ b/pyload/__init__.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+
+__all__ = ["__status_code__", "__status__", "__version_info__", "__version__", "__author_name__", "__author_mail__", "__license__"]
+
+__version_info__ = (0, 4, 10)
+__version__ = '.'.join(map(str, __version_info__))
+
+__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
+
+__description__ = "Fast, lightweight and full featured download manager"
+
+__license__ = "GNU General Public License v3"
+
+__website__ = "http://pyload.org"
+
+__authors__ = [("Marius" , "mkaay@mkaay.de" ),
+ ("RaNaN" , "Mast3rRaNaN@hotmail.de"),
+ ("Stefano" , "l.stickell@yahoo.it" ),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("himbrr" , "himbrr@himbrr.ws" ),
+ ("sebnapi" , "" ),
+ ("spoob" , "spoob@gmx.de" ),
+ ("zoidberg10" , "zoidberg@mujmail.cz" )]
+
+
+################################# InitHomeDir #################################
+
+import __builtin__
+import os
+
+__builtin__.owd = os.path.abspath("") #: original working directory
+__builtin__.homedir = os.path.expanduser("~")
+__builtin__.rootdir = os.path.abspath(os.path.join(__file__, ".."))
+__builtin__.configdir = ""
+__builtin__.pypath = os.path.abspath(os.path.join(rootdir, ".."))
+
+
+import sys
+
+sys.path.append(os.path.join(pypath, "lib", "Python", "Lib"))
+sys.path.append(os.path.join(pypath, "lib"))
+
+
+import codecs
+
+from pyload.utils import get_console_encoding
+
+sys.stdout = codecs.getwriter(get_console_encoding(sys.stdout.encoding))(sys.stdout, errors="replace")
+
+
+if homedir == "~" and os.name == "nt":
+ 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)
+
+ _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf)
+
+ __builtin__.homedir = path_buf.value
+
+
+try:
+ p = os.path.join(rootdir, "config", "configdir")
+
+ with open(p, "rb") as f:
+ configdir = f.read().strip()
+
+except IOError:
+ if os.name == "posix":
+ configdir = os.path.join(homedir, ".pyload")
+ else:
+ configdir = os.path.join(homedir, "pyload")
+
+
+try:
+ if not os.path.exists(configdir):
+ os.makedirs(configdir, 0700)
+
+ os.chdir(configdir)
+
+except IOError, e:
+ print >> sys.stderr, "configdir init failed: %d (%s)" % (e.errno, e.strerror)
+ sys.exit(1)
+
+else:
+ __builtin__.configdir = configdir
diff --git a/pyload/config/Parser.py b/pyload/config/Parser.py
new file mode 100644
index 000000000..f834e4b4e
--- /dev/null
+++ b/pyload/config/Parser.py
@@ -0,0 +1,357 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import shutil
+import time
+import traceback
+
+from pyload.utils import chmod, encode, decode
+
+
+CONF_VERSION = 1
+
+
+class ConfigParser(object):
+ """
+ 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()
+
+
+ def checkVersion(self, n=0):
+ """determines if config need to be copied"""
+ try:
+ if not os.path.exists("pyload.conf"):
+ shutil.copy(os.path.join(pypath, "pyload", "config", "default.conf"), "pyload.conf")
+
+ if not os.path.exists("plugin.conf"):
+ with open("plugin.conf", "wb") as f:
+ f.write("version: " + str(CONF_VERSION))
+
+ with open("pyload.conf", "rb") as f:
+ v = f.readline()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ shutil.copy(os.path.join(pypath, "pyload", "config", "default.conf"), "pyload.conf")
+ print "Old version of config was replaced"
+
+ with open("plugin.conf", "rb") as f:
+ v = f.readline()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ with open("plugin.conf", "wb") as f:
+ f.write("version: " + str(CONF_VERSION))
+ print "Old version of plugin-config replaced"
+
+ except Exception:
+ if n >= 3:
+ raise
+ time.sleep(0.3)
+ self.checkVersion(n + 1)
+
+
+ def readConfig(self):
+ """reads the config file"""
+ self.config = self.parseConfig(os.path.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:
+ print "Config Warning"
+ traceback.print_exc()
+
+
+ def parseConfig(self, config):
+ """parses a given configfile"""
+
+ with open(config) as f:
+ 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,
+ "idx": len(conf[section])}
+
+
+ else:
+ content, none, value = line.partition("=")
+
+ content, none, desc = content.partition(":")
+
+ desc = desc.replace('"', "").strip()
+
+ typ, none, option = content.strip().rpartition(" ")
+
+ value = value.strip()
+ typ = typ.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,
+ "idx": len(conf[section])}
+
+ except Exception, e:
+ print "Config Warning"
+ traceback.print_exc()
+
+ 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:
+ os.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 sorted(config[section].items(), key=lambda i: i[1]['idx'] if i[0] not in ("desc", "outline") else 0):
+ 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 isinstance(data['value'], basestring):
+ 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'], encode(value)))
+
+
+ def cast(self, typ, value):
+ """cast value to given format"""
+ if not isinstance(value, basestring):
+ return value
+
+ elif typ == "int":
+ return int(value)
+ elif typ == "bool":
+ return value.lower() in ("1", "true", "on", "an", "yes")
+ elif typ == "time":
+ if not value:
+ value = "0:00"
+ if not ":" in value:
+ value += ":00"
+ return value
+ elif typ in ("str", "file", "folder"):
+ return encode(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"""
+ value = self.config[section][option]['value']
+ return decode(value)
+
+
+ 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"""
+ value = self.plugin[plugin][option]['value']
+ return encode(value)
+
+
+ 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 removeDeletedPlugins(self, plugins):
+ for name in self.plugin.keys():
+ if not name in plugins:
+ del self.plugin[name]
+
+
+ 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]),
+ "idx": len(conf)
+ }
+
+ 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]
+
+
+class Section(object):
+ """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..fdf2524f5
--- /dev/null
+++ b/pyload/config/Setup.py
@@ -0,0 +1,565 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+from __future__ import with_statement
+
+import __builtin__
+import getpass
+import os
+import subprocess
+import sys
+
+from pyload.network.JsEngine import JsEngine
+from pyload.utils import get_console_encoding, load_translation, fs_join, versiontuple
+
+
+class SetupAssistant(object):
+ """ pyLoads initial setup configuration assistant """
+
+ def __init__(self, config):
+ self.config = config
+ self.lang = "en"
+ self.stdin_encoding = get_console_encoding(sys.stdin.encoding)
+
+ # Input shorthand for yes
+ self.yes = "y"
+ # Input shorthand for no
+ self.no = "n"
+
+
+ def start(self):
+ print
+ langs = sorted(self.config.getMetaData("general", "language")['type'].split(";"))
+ self.lang = self.ask(u"Choose setup language", "en", langs)
+
+ load_translation("setup", self.lang)
+
+ # 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)
+ # ...
+
+ print
+ print
+ print _("## Welcome to the pyLoad Configuration Assistant ##")
+ print
+ 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"))
+
+ if avail:
+ print _("AVAILABLE FEATURES:")
+ for feature in avail:
+ print feature
+ 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.")
+ else:
+ print _("NO MISSING FEATURES!")
+
+ print
+ print
+ con = self.ask(_("Continue with setup?"), self.yes, bool=True)
+
+ if not con:
+ return False
+
+ print
+ print
+ print _("CURRENT CONFIG PATH: %s") % configdir
+ 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.")
+ confpath = self.ask(_("Do you want to change the config path?"), self.no, bool=True)
+ if confpath:
+ print
+ self.conf_path()
+ print
+
+ 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 """
+ import platform
+
+ print _("## System Information ##")
+ print
+ print _("Platform: ") + platform.platform(aliased=True)
+ print _("OS: ") + platform.system() or "Unknown"
+ print _("Python: ") + sys.version.replace("\n", "")
+ print
+ print
+
+ print _("## System Check ##")
+ print
+
+ if (2, 5) > sys.version_info > (2, 7):
+ python = False
+ else:
+ python = True
+
+ self.print_dep("python", python, false="NOT OK")
+
+ 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([path.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 Exception:
+ jinja = False
+ jinja_error = "MISSING"
+ else:
+ jinja_error = "NOT OK"
+
+ self.print_dep("jinja2", jinja, false=jinja_error)
+
+ beaker = self.check_module("beaker")
+ self.print_dep("beaker", beaker)
+
+ bjoern = self.check_module("bjoern")
+ self.print_dep("bjoern", bjoern)
+
+ web = sqlite and beaker
+
+ js = bool(JsEngine.find())
+ self.print_dep(_("JS engine"), js)
+
+ if not python:
+ print
+ print
+ if sys.version_info > (2, 7):
+ print _("WARNING: Your python version is too NEW!")
+ print _("Please use Python version 2.6/2.7 .")
+ else:
+ print _("WARNING: Your python version is too OLD!")
+ print _("Please use at least Python version 2.5 .")
+
+ if not jinja and jinja_error == "NOT OK":
+ print
+ print
+ print _("WARNING: Your installed jinja2 version %s is 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 to your local network from outside (ex. internet).")
+ print
+ username = self.ask(_("Username"), "User")
+ password = self.ask("", "", password=True)
+ db.addUser(username, password)
+ db.shutdown()
+
+ print
+ 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.set("remote", "activated", self.ask(_("Enable remote access"), self.no, bool=True))
+
+ print
+ langs = sorted(self.config.getMetaData("general", "language")['type'].split(";"))
+ self.config.set("general", "language", self.ask(_("Choose system language"), self.lang, langs))
+
+ print
+ self.config.set("general", "download_folder", self.ask(_("Download folder"), "Downloads"))
+ print
+ self.config.set("download", "max_downloads", self.ask(_("Max parallel downloads"), "3"))
+ print
+ reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True)
+ self.config.set("reconnect", "activated", reconnect)
+ if reconnect:
+ self.config.set("reconnect", "method", self.ask(_("Reconnect script location"), "./reconnect.sh"))
+
+
+ def conf_web(self):
+ print _("## Webinterface Setup ##")
+
+ print
+ print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.")
+ self.config.set("webui", "host", self.ask(_("Address"), "0.0.0.0"))
+ self.config.set("webui", "port", self.ask(_("Port"), "8000"))
+ print
+ print _("pyLoad offers several server backends, now following a short explanation.")
+ print "- auto:", _("Automatically choose the best webserver for your platform.")
+ print "- builtin:", _("First 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 not works correctly, so if you have troubles with the web interface")
+ print _("run this setup assistant again and change the builtin server to the threaded.")
+
+ if os.name == "nt":
+ servers = ["auto", "builtin", "threaded", "fastcgi"]
+ else:
+ servers = ["auto", "builtin", "threaded", "fastcgi", "lightweight"]
+
+ self.config.set("webui", "server", self.ask(_("Choose webserver"), "auto", 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.")
+
+ ssl = self.ask(_("Activate SSL?"), self.yes, bool=True)
+ self.config.set("remote", "ssl", ssl)
+ self.config.set("webui", "ssl", ssl)
+
+
+ def set_user(self):
+ load_translation("setup", self.config.get("general", "language"))
+
+ 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 set_configdir(self, configdir, persistent=False):
+ dirname = path.abspath(configdir)
+ try:
+ if not path.exists(dirname):
+ os.makedirs(dirname, 0700)
+
+ os.chdir(dirname)
+
+ if persistent:
+ c = path.join(rootdir, "config", "configdir")
+ if not path.exists(c):
+ os.makedirs(c, 0700)
+
+ with open(c, "wb") as f:
+ f.write(dirname)
+
+ except IOError:
+ return False
+
+ else:
+ __builtin__.configdir = dirname
+ return dirname #: return always abspath
+
+
+ def conf_path(self):
+ print _("Setting new config path.")
+ print _("NOTE: Current configuration will not be transfered!")
+
+ while True:
+ confdir = self.ask(_("CONFIG PATH"), configdir)
+ confpath = self.set_configdir(confdir)
+ print
+ if not confpath:
+ print _("Failed to change the current CONFIG PATH!")
+ print
+ else:
+ print _("CONFIG PATH successfully changed to: %s") % configdir
+ break
+
+
+ def print_dep(self, name, value, false="MISSING", true="OK"):
+ """ Print Status of dependency """
+ if value and isinstance(value, basestring):
+ info = ", ".join(value)
+ else:
+ info = ""
+
+ print "%(dep)-12s %(bool)s (%(info)s)" % {'dep': name + ':',
+ 'bool': _(true if value else false).upper(),
+ 'info': info}
+
+
+ def check_module(self, module):
+ try:
+ __import__(module)
+ return True
+ except Exception:
+ return False
+
+
+ def check_prog(self, command):
+ pipe = PIPE
+ try:
+ subprocess.call(command, stdout=pipe, stderr=pipe)
+ return True
+ except Exception:
+ 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:
+ sys.stdout.write(_("Password: "))
+ p1 = getpass.getpass("").strip("\n\r")
+
+ if len(p1) < pwlen:
+ print
+ print _("Password too short! Use at least %s symbols." % pwlen)
+ print
+ continue
+ elif not p1.isalnum():
+ print
+ print _("Password must be alphanumeric.")
+ print
+ continue
+
+ sys.stdout.write(_("Password (again): "))
+ p2 = getpass.getpass("").strip("\n\r")
+
+ if p1 == p2:
+ print
+ if self.ask(_("Show password?"), self.no, bool=True):
+ print
+ print _("Your Password is: %s") % p1
+ return p1
+ else:
+ print
+ 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")
+ print
+ continue
+
+ if not answers or input in answers:
+ return input
+ else:
+ print _("Invalid Input")
diff --git a/pyload/config/__init__.py b/pyload/config/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/config/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/config/default.conf b/pyload/config/default.conf
new file mode 100644
index 000000000..c70d4d71a
--- /dev/null
+++ b/pyload/config/default.conf
@@ -0,0 +1,76 @@
+version: 1
+
+remote - "Remote":
+ bool activated : "Activated" = True
+ int port : "Port" = 7227
+ ip listenaddr : "Address" = 0.0.0.0
+ bool nolocalauth : "No authentication on local connections" = True
+
+ssl - "SSL":
+ bool activated : "Activated" = False
+ file cert : "SSL Certificate" = ssl.crt
+ file key : "SSL Key" = ssl.key
+
+webui - "Web User Interface":
+ bool activated : "Activated" = True
+ auto;threaded;fastcgi;lightweight server : "Server" = auto
+ bool https : "Use HTTPS" = False
+ ip host : "IP" = 0.0.0.0
+ int port : "Port" = 8001
+ Default;Dark;Flat;Next theme : "Theme" = Next
+ str prefix : "Path Prefix" = None
+
+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
+ bool color_console : "Colored console" = True
+ label;line console_mode : "Colored console mode" = line
+
+general - "General":
+ en; language : "Language" = en
+ folder download_folder : "Download Folder" = Downloads
+ bool debug_mode : "Debug Mode" = False
+ int min_free_space : "Min Free Space in MB" = 200
+ bool folder_per_package : "Create folder for each package" = True
+ int renice : "CPU Priority" = 0
+ auto;common;pyv8;node;rhino;jsc jsengine : "JS Engine" = auto
+
+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 : "Activated" = 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/manager/Account.py b/pyload/manager/Account.py
new file mode 100644
index 000000000..b743ccabc
--- /dev/null
+++ b/pyload/manager/Account.py
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import shutil
+import threading
+
+from pyload.manager.Event import AccountUpdateEvent
+from pyload.utils import chmod, lock
+
+
+ACC_VERSION = 1
+
+
+class AccountManager(object):
+ """manages all accounts"""
+
+ def __init__(self, core):
+ """Constructor"""
+
+ self.core = core
+ self.lock = threading.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"""
+ try:
+ if plugin in self.accounts:
+ if plugin not in self.plugins:
+ klass = self.core.pluginManager.loadClass("accounts", plugin)
+ if klass:
+ self.plugins[plugin] = klass(self, self.accounts[plugin])
+ else: #@NOTE: The account class no longer exists (blacklisted plugin). Skipping the account to avoid crash
+ raise
+
+ return self.plugins[plugin]
+ else:
+ raise
+ except Exception:
+ 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"""
+
+ try:
+ with open("accounts.conf", "a+") as f:
+ content = f.readlines()
+ version = content[0].split(":")[1].strip() if content else ""
+
+ if not version or int(version) < ACC_VERSION:
+ shutil.copy("accounts.conf", "accounts.backup")
+ f.seek(0)
+ f.write("version: " + str(ACC_VERSION))
+
+ self.core.log.warning(_("Account settings deleted, due to new config format"))
+ return
+
+ except IOError, e:
+ self.core.log.error(str(e))
+ 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 Exception:
+ 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"""
+
+ try:
+ with open("accounts.conf", "wb") as f:
+ 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)))
+
+ os.chmod(f.name, 0600)
+
+ except Exception, e:
+ self.core.log.error(str(e))
+
+
+ #----------------------------------------------------------------------
+
+ 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.__class__.__name__] = p.getAllAccounts(force)
+ else: #@NOTE: 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/Addon.py b/pyload/manager/Addon.py
new file mode 100644
index 000000000..a632111ea
--- /dev/null
+++ b/pyload/manager/Addon.py
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+# @interface-version: 0.1
+
+import __builtin__
+
+import threading
+import traceback
+import types
+
+from pyload.Thread import AddonThread
+from pyload.manager.Plugin import literal_eval
+from pyload.utils import lock
+
+
+class AddonManager(object):
+ """Manages addons, delegates and handles Events.
+
+ Every plugin can define events, \
+ but some very usefull events are called by the Core.
+ Contrary to overwriting addon 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 addon methods exists as events. These are the additional known events.
+
+ ======================= ============== ==================================
+ Name Arguments Description
+ ======================= ============== ==================================
+ download-preparing fid A download was just queued and will be prepared now.
+ download-start fid A plugin will immediately starts the download afterwards.
+ links-added links, pid Someone just added links, you are able to modify the links.
+ all_downloads-processed Every link was handled, pyload would idle afterwards.
+ all_downloads-finished Every download in queue is finished.
+ config-changed The config was changed via the api.
+ pluginConfigChanged The plugin config changed, due to api or internal process.
+ ======================= ============== ==================================
+
+ | Notes:
+ | all_downloads-processed is *always* called before all_downloads-finished.
+ | config-changed is *always* called before pluginConfigChanged.
+ """
+
+ def __init__(self, core):
+ self.core = core
+
+ __builtin__.addonManager = self #: needed to let addons register themself
+
+ 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.core.config.pluginCB = types.MethodType(self.dispatchEvent, "pluginConfigChanged", basestring) #@TODO: Rename event pluginConfigChanged
+
+ self.addEvent("pluginConfigChanged", self.manageAddon)
+
+ self.lock = threading.RLock()
+ self.createIndex()
+
+
+ def try_catch(func):
+
+
+ def new(*args):
+ try:
+ return func(*args)
+ except Exception, e:
+ args[0].core.log.error(_("Error executing addon: %s") % 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 = ()
+ 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 = []
+
+ for type in ("addon", "hook"):
+ active = []
+ deactive = []
+ for pluginname in getattr(self.core.pluginManager, "%sPlugins" % type):
+ try:
+ if self.core.config.getPlugin("%s_%s" % (pluginname, type), "activated"):
+ pluginClass = self.core.pluginManager.loadClass(type, 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 Exception:
+ self.core.log.warning(_("Failed activating %(name)s") % {"name": pluginname})
+ if self.core.debug:
+ traceback.print_exc()
+
+ self.core.log.info(_("Activated %ss: %s") % (type, ", ".join(sorted(active))))
+ self.core.log.info(_("Deactivated %ss: %s") % (type, ", ".join(sorted(deactive))))
+
+ self.plugins = plugins
+
+
+ def manageAddon(self, plugin, name, value):
+ if name == "activated" and value:
+ self.activateAddon(plugin)
+
+ elif name == "activated" and not value:
+ self.deactivateAddon(plugin)
+
+
+ def activateAddon(self, pluginname):
+ # check if already loaded
+ for inst in self.plugins:
+ if inst.__class__.__name__ == pluginname:
+ return
+
+ pluginClass = self.core.pluginManager.loadClass("addon", pluginname)
+
+ if not pluginClass:
+ return
+
+ self.core.log.debug("Activate addon: %s" % pluginname)
+
+ addon = pluginClass(self.core, self)
+ self.plugins.append(addon)
+ self.pluginMap[pluginClass.__name__] = addon
+
+ addon.activate()
+
+
+ def deactivateAddon(self, pluginname):
+ for plugin in self.plugins:
+ if plugin.__class__.__name__ == pluginname:
+ addon = plugin
+ break
+ else:
+ return
+
+ self.core.log.debug("Deactivate addon: %s" % pluginname)
+
+ addon.deactivate()
+
+ # remove periodic call
+ self.core.log.debug("Removed callback: %s" % self.core.scheduler.removeJob(addon.cb))
+
+ self.plugins.remove(addon)
+ del self.pluginMap[addon.__class__.__name__]
+
+
+ @try_catch
+ def coreReady(self):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.activate()
+
+ self.dispatchEvent("addon-start")
+
+
+ @try_catch
+ def coreExiting(self):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.exit()
+
+ self.dispatchEvent("addon-exit")
+
+
+ @lock
+ def downloadPreparing(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadPreparing(pyfile)
+
+ self.dispatchEvent("download-preparing", pyfile)
+
+
+ @lock
+ def downloadFinished(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadFinished(pyfile)
+
+ self.dispatchEvent("download-finished", pyfile)
+
+
+ @lock
+ @try_catch
+ def downloadFailed(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadFailed(pyfile)
+
+ self.dispatchEvent("download-failed", pyfile)
+
+
+ @lock
+ def packageFinished(self, package):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.packageFinished(package)
+
+ self.dispatchEvent("package-finished", 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):
+ return AddonThread(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 addon 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.core.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/Captcha.py b/pyload/manager/Captcha.py
new file mode 100644
index 000000000..271e6122b
--- /dev/null
+++ b/pyload/manager/Captcha.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+import threading
+import time
+import traceback
+
+from pyload.utils import encode
+
+
+class CaptchaManager(object):
+
+ def __init__(self, core):
+ self.lock = threading.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, timeout=50):
+ cli = self.core.isClientConnected()
+
+ if cli: #: client connected -> should solve the captcha
+ task.setWaiting(timeout) #: wait 50 sec for response
+
+ for plugin in self.core.addonManager.activePlugins():
+ try:
+ plugin.captchaTask(task)
+ except Exception:
+ if self.core.debug:
+ traceback.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(object):
+
+ 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 Exception:
+ self.result = None
+
+
+ def getResult(self):
+ return encode(self.result)
+
+
+ def getStatus(self):
+ return self.status
+
+
+ def setWaiting(self, sec):
+ """ let the captcha wait secs for the solution """
+ self.waitUntil = max(time.time() + sec, self.waitUntil)
+ self.status = "waiting"
+
+
+ def isWaiting(self):
+ if self.result or self.error or self.timedOut():
+ return False
+ else:
+ 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.time() > self.waitUntil
+
+
+ def invalid(self):
+ """ indicates the captcha was not correct """
+ for x in self.handler:
+ x.captchaInvalid(self)
+
+
+ def correct(self):
+ for x in self.handler:
+ x.captchaCorrect(self)
+
+
+ def __str__(self):
+ return "<CaptchaTask '%s'>" % self.id
diff --git a/pyload/manager/Event.py b/pyload/manager/Event.py
new file mode 100644
index 000000000..919835984
--- /dev/null
+++ b/pyload/manager/Event.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+import time
+
+from pyload.utils import uniqify
+
+
+class PullManager(object):
+
+ 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.time():
+ del self.clients[n]
+
+
+ def getEvents(self, uuid):
+ events = []
+ validUuid = False
+ for client in self.clients:
+ if client.uuid == uuid:
+ client.lastActive = time.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(object):
+
+ def __init__(self, uuid):
+ self.uuid = uuid
+ self.lastActive = time.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(object):
+
+ 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(object):
+
+ 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(object):
+
+ 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(object):
+
+ def __init__(self, destination):
+ assert destination == "queue" or destination == "collector"
+ self.destination = destination
+
+
+ def toList(self):
+ return ["reload", self.destination]
+
+
+class AccountUpdateEvent(object):
+
+ def toList(self):
+ return ["account"]
+
+
+class ConfigUpdateEvent(object):
+
+ def toList(self):
+ return ["config"]
diff --git a/pyload/manager/Plugin.py b/pyload/manager/Plugin.py
new file mode 100644
index 000000000..03bf9cacc
--- /dev/null
+++ b/pyload/manager/Plugin.py
@@ -0,0 +1,400 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+import sys
+import traceback
+import urllib
+
+import SafeEval
+
+
+class PluginManager(object):
+ ROOT = "pyload.plugin."
+ USERROOT = "userplugins."
+ TYPES = ["account", "addon", "container", "crypter", "extractor", "hook", "hoster", "internal", "ocr"]
+
+ PATTERN = re.compile(r'__pattern\s*=\s*u?r("|\')([^"\']+)')
+ VERSION = re.compile(r'__version\s*=\s*("|\')([\d.]+)')
+ CONFIG = re.compile(r'__config\s*=\s*\[([^\]]+)', re.M)
+ DESC = re.compile(r'__description\s*=\s*("|"""|\')([^"\']+)')
+
+
+ def __init__(self, core):
+ self.core = core
+
+ self.plugins = {}
+ self.createIndex()
+
+ # register for import addon
+ sys.meta_path.append(self)
+
+
+ def loadTypes(self):
+ rootdir = os.path.join(pypath, "pyload", "plugin")
+ userdir = "userplugins"
+
+ types = set().union(*[[d for d in os.listdir(p) if os.path.isdir(os.path.join(p, d))]
+ for p in (rootdir, userdir) if os.path.exists(p)])
+
+ if not types:
+ self.core.log.critical(_("No plugins found!"))
+
+ self.TYPES = list(set(self.TYPES) | types)
+
+
+ def createIndex(self):
+ """create information for all plugins available"""
+
+ sys.path.append(os.path.abspath(""))
+
+ self.loadTypes()
+
+ configs = []
+
+ for type in self.TYPES:
+ self.plugins[type] = self.parse(type)
+ setattr(self, "%sPlugins" % type, self.plugins[type])
+ configs.extend("%s_%s" % (p, type) for p in self.plugins[type])
+
+ self.core.config.removeDeletedPlugins(configs)
+
+ self.core.log.debug("Created index of plugins")
+
+
+ def parse(self, folder, rootplugins={}):
+ """
+ returns dict with information
+ home contains parsed plugins from pyload.
+ """
+
+ plugins = {}
+
+ if rootplugins:
+ try:
+ pfolder = os.path.join("userplugins", folder)
+ if not os.path.exists(pfolder):
+ os.makedirs(pfolder)
+
+ for ifile in (os.path.join("userplugins", "__init__.py"),
+ os.path.join(pfolder, "__init__.py")):
+ if not os.path.exists(ifile):
+ f = open(ifile, "wb")
+ f.close()
+
+ except IOError, e:
+ self.core.log.critical(str(e))
+ return rootplugins
+
+ else:
+ pfolder = os.path.join(pypath, "pyload", "plugin", folder)
+
+ for f in os.listdir(pfolder):
+ if os.path.isfile(os.path.join(pfolder, f)) and f.endswith(".py") and not f.startswith("_"):
+
+ try:
+ with open(os.path.join(pfolder, f)) as data:
+ content = data.read()
+
+ except IOError, e:
+ self.core.log.error(str(e))
+ continue
+
+ name = f[:-3]
+ if name[-1] == ".":
+ name = name[:-4]
+
+ if not re.search("class\\s+%s\\(" % name, content):
+ self.core.log.error(_("invalid classname: %s ignored") % os.path.join(pfolder, f))
+
+ version = self.VERSION.findall(content)
+ if version:
+ version = float(version[0][1])
+ else:
+ version = 0
+
+ if rootplugins and name in rootplugins:
+ if rootplugins[name]['version'] >= version:
+ continue
+
+ plugins[name] = {}
+ plugins[name]['version'] = version
+
+ module = f.replace(".pyc", "").replace(".py", "")
+
+ # the plugin is loaded from user directory
+ plugins[name]['user'] = bool(rootplugins)
+ plugins[name]['name'] = module
+
+ pattern = self.PATTERN.findall(content)
+
+ if pattern:
+ pattern = pattern[0][1]
+
+ try:
+ regexp = re.compile(pattern)
+ except Exception:
+ self.core.log.error(_("%s has a invalid pattern") % name)
+ pattern = r'^unmatchable$'
+ regexp = re.compile(pattern)
+
+ plugins[name]['pattern'] = pattern
+ plugins[name]['re'] = regexp
+
+ # internals have no config
+ if folder == "internal":
+ self.core.config.deleteConfig("internal")
+ continue
+
+ config = self.CONFIG.findall(content)
+ if config:
+ try:
+ config = SafeEval.const_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 not in ("account", "internal") and not [True for item in config if item[0] == "activated"]:
+ config.insert(0, ["activated", "bool", "Activated", not folder in ("addon", "hook")])
+
+ self.core.config.addPluginConfig("%s_%s" % (name, folder), config, desc)
+ except Exception:
+ self.core.log.error("Invalid config in %s: %s" % (name, config))
+
+ elif folder in ("addon", "hook"): #: 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("%s_%s" % (name, folder), config, desc)
+ except Exception:
+ self.core.log.error("Invalid config in %s: %s" % (name, config))
+
+ if not rootplugins and plugins: #: Double check
+ plugins.update(self.parse(folder, plugins))
+
+ return plugins
+
+
+ def parseUrls(self, urls):
+ """parse plugins for given list of urls"""
+
+ last = None
+ res = [] #: tupels of (url, plugintype, pluginname)
+
+ for url in urls:
+ if type(url) not in (str, unicode, buffer):
+ continue
+
+ url = urllib.unquote(url)
+
+ if last and last[2]['re'].match(url):
+ res.append((url, last[0], last[1]))
+ continue
+
+ for plugintype in self.TYPES:
+ m = None
+ for name, plugin in self.plugins[plugintype].iteritems():
+ try:
+ if 'pattern' in plugin:
+ m = plugin['re'].match(url)
+
+ except KeyError:
+ self.core.log.error(_("Plugin [%(type)s] %(name)s skipped due broken pattern")
+ % {'name': plugin['name'], 'type': plugintype})
+
+ if m:
+ res.append((url, plugintype, name))
+ last = (plugintype, name, plugin)
+ break
+ if m:
+ break
+ else:
+ res.append((url, "internal", "BasePlugin"))
+ print res
+ return res
+
+
+ def findPlugin(self, type, name):
+ if isinstance(type, tuple):
+ for typ in type:
+ if name in self.plugins[typ]:
+ return (self.plugins[typ][name], typ)
+
+ if isinstance(type, tuple) or type not in self.plugins or name not in self.plugins[type]:
+ self.core.log.warning(_("Plugin [%(type)s] %(name)s not found | Using plugin: [internal] BasePlugin")
+ % {'name': name, 'type': type})
+ return self.internalPlugins['BasePlugin']
+
+ else:
+ return self.plugins[type][name]
+
+
+ def getPlugin(self, type, name, original=False):
+ """return plugin module from hoster|decrypter|container"""
+ plugin = self.findPlugin(type, name)
+
+ if plugin is None:
+ return {}
+
+ if "new_module" in plugin and not original:
+ return plugin['new_module']
+ else:
+ return self.loadModule(type, name)
+
+
+ def getPluginName(self, type, name):
+ """ used to obtain new name if other plugin was injected"""
+ plugin = self.findPlugin(type, name)
+
+ if plugin is None:
+ return ""
+
+ 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'])
+
+ except Exception, e:
+ self.core.log.error(_("Error importing plugin: [%(type)s] %(name)s (v%(version).2f) | %(errmsg)s")
+ % {'name': name, 'type': type, 'version': plugins[name]['version'], "errmsg": str(e)})
+ if self.core.debug:
+ traceback.print_exc()
+
+ else:
+ plugins[name]['module'] = module # : cache import, maybe unneeded
+
+ self.core.log.debug(_("Loaded plugin: [%(type)s] %(name)s (v%(version).2f)")
+ % {'name': name, 'type': type, 'version': plugins[name]['version']})
+ return module
+
+
+ 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)
+ else:
+ return None
+
+
+ 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.core.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.core.log.debug("Request reload of plugins: %s" % type_plugins)
+
+ reloaded = []
+
+ as_dict = {}
+ for t, n in type_plugins:
+ if t in as_dict:
+ as_dict[t].append(n)
+ else:
+ as_dict[t] = [n]
+
+ for type in as_dict.iterkeys():
+ if type in ("addon", "internal"): # : do not reload them because would cause to much side effects
+ self.core.log.debug("Skipping reload for plugins from type: %(type)s" % {'type': type})
+ continue
+
+ for plugin in as_dict[type]:
+ if plugin in self.plugins[type] and "module" in self.plugins[type][plugin]:
+ self.core.log.debug(_("Reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type})
+
+ try:
+ reload(self.plugins[type][plugin]['module'])
+
+ except Exception, e:
+ self.core.log.error(_("Error when reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}, e)
+ continue
+
+ else:
+ reloaded.append((type, plugin))
+
+ # index creation
+ self.plugins[type] = self.parse(type)
+ setattr(self, "%sPlugins" % type, self.plugins[type])
+
+ if "account" 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 bool(self.reloadPlugins(type_plugin))
diff --git a/pyload/manager/Remote.py b/pyload/manager/Remote.py
new file mode 100644
index 000000000..6023cdcfd
--- /dev/null
+++ b/pyload/manager/Remote.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import threading
+import traceback
+
+
+class BackendBase(threading.Thread):
+
+ def __init__(self, manager):
+ threading.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:
+ traceback.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(object):
+ 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.get("remote", "listenaddr")
+ port = self.core.config.get("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:
+ traceback.print_exc()
+ else:
+ backend.start()
+ self.backends.append(backend)
+
+ port += 1
diff --git a/pyload/manager/Scheduler.py b/pyload/manager/Scheduler.py
new file mode 100644
index 000000000..b82768aff
--- /dev/null
+++ b/pyload/manager/Scheduler.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# @author: mkaay
+
+import heapq
+import threading
+import time
+
+
+class AlreadyCalled(Exception):
+ pass
+
+
+class Deferred(object):
+
+ 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(object):
+
+ def __init__(self, core):
+ self.core = core
+
+ self.queue = PriorityQueue()
+
+
+ def addJob(self, t, call, args=[], kwargs={}, threaded=True):
+ d = Deferred()
+ t += time.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.time():
+ j.start()
+ else:
+ self.queue.put((t, j))
+ break
+
+
+class Job(object):
+
+ 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:
+ t = threading.Thread(target=self.run)
+ t.setDaemon(True)
+ t.start()
+ else:
+ self.run()
+
+
+class PriorityQueue(object):
+ """ a non blocking priority queue """
+
+ def __init__(self):
+ self.queue = []
+ self.lock = threading.Lock()
+
+
+ def __iter__(self):
+ return iter(self.queue)
+
+
+ def __delitem__(self, key):
+ del self.queue[key]
+
+
+ def put(self, element):
+ self.lock.acquire()
+ heapq.heappush(self.queue, element)
+ self.lock.release()
+
+
+ def get(self):
+ """ return element or None """
+ self.lock.acquire()
+ try:
+ el = heapq.heappop(self.queue)
+ return el
+ except IndexError:
+ return None, None
+ finally:
+ self.lock.release()
diff --git a/pyload/manager/Thread.py b/pyload/manager/Thread.py
new file mode 100644
index 000000000..b80c5594a
--- /dev/null
+++ b/pyload/manager/Thread.py
@@ -0,0 +1,313 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import os
+import random
+import re
+import subprocess
+import threading
+import time
+import traceback
+
+import pycurl
+
+from pyload.Datatype import PyFile
+from pyload.Thread import DecrypterThread, DownloadThread, InfoThread
+from pyload.network.RequestFactory import getURL
+from pyload.utils import freeSpace, lock
+
+
+class ThreadManager(object):
+ """manages the download threads, assign jobs, reconnect etc"""
+
+ def __init__(self, core):
+ """Constructor"""
+ self.core = core
+
+ self.threads = [] #: thread list
+ self.localThreads = [] #: addon+decrypter threads
+
+ self.pause = True
+
+ self.reconnecting = threading.Event()
+ self.reconnecting.clear()
+ self.downloaded = 0 #: number of files downloaded since last cleanup
+
+ self.lock = threading.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 xrange(0, self.core.config.get("download", "max_downloads")):
+ self.createThread()
+
+
+ def createThread(self):
+ """create a download thread"""
+
+ thread = 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.time() + 5 * 60
+
+ InfoThread(self, data, pid)
+
+
+ @lock
+ def createResultThread(self, data, add=False):
+ """ creates a thread to fetch online status, returns result id """
+ self.timestamp = time.time() + 5 * 60
+
+ rid = self.resultIDs
+ self.resultIDs += 1
+
+ InfoThread(self, data, rid=rid, add=add)
+
+ return rid
+
+
+ @lock
+ def getInfoResult(self, rid):
+ """returns result and clears it"""
+ self.timestamp = time.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.core.log.error(_("Reconnect Failed: %s") % str(e))
+ self.reconnecting.clear()
+ if self.core.debug:
+ traceback.print_exc()
+ self.checkThreadCount()
+
+ try:
+ self.assignJob()
+ except Exception, e:
+ self.core.log.warning("Assign job error", e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ time.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.time():
+ self.infoCache.clear()
+ self.infoResults.clear()
+ self.core.log.debug("Cleared Result cache")
+
+
+ #--------------------------------------------------------------------------
+
+ def tryReconnect(self):
+ """checks if reconnect needed"""
+
+ if not (self.core.config.get("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 os.path.exists(self.core.config.get("reconnect", "method")):
+ if os.path.exists(os.path.join(pypath, self.core.config.get("reconnect", "method"))):
+ self.core.config.set("reconnect", "method", os.path.join(pypath, self.core.config.get("reconnect", "method")))
+ else:
+ self.core.config.set("reconnect", "activated", False)
+ self.core.log.warning(_("Reconnect script not found!"))
+ return
+
+ self.reconnecting.set()
+
+ # Do reconnect
+ self.core.log.info(_("Starting reconnect"))
+
+ while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0:
+ time.sleep(0.25)
+
+ ip = self.getIP()
+
+ self.core.addonManager.beforeReconnecting(ip)
+
+ self.core.log.debug("Old IP: %s" % ip)
+
+ try:
+ reconn = subprocess.Popen(self.core.config.get("reconnect", "method"), bufsize=-1, shell=True) # , stdout=subprocess.PIPE)
+ except Exception:
+ self.core.log.warning(_("Failed executing reconnect script!"))
+ self.core.config.set("reconnect", "activated", False)
+ self.reconnecting.clear()
+ if self.core.debug:
+ traceback.print_exc()
+ return
+
+ reconn.wait()
+ time.sleep(1)
+ ip = self.getIP()
+ self.core.addonManager.afterReconnecting(ip)
+
+ self.core.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 xrange(10):
+ try:
+ sv = random.choice(services)
+ ip = getURL(sv[0])
+ ip = re.match(sv[1], ip).group(1)
+ break
+ except Exception:
+ ip = ""
+ time.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.core.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.plugintype, x.active.pluginname), self.getLimit(x)) for x in self.threads if x.active and isinstance(x.active, PyFile) and x.active.hasPlugin() and x.active.plugin.account])
+ inuse = map(lambda x: ('.'.join(x[0]), x[1], len([y for y in self.threads if y.active and isinstance(y.active, PyFile) and y.active.plugintype == x[0][0] and y.active.pluginname == x[0][1]])), inuse)
+ onlimit = [x[0] for x in inuse if x[1] > 0 and x[2] >= x[1]]
+
+ occ = [x.active.plugintype + '.' + x.active.pluginname for x in self.threads if x.active and isinstance(x.active, PyFile) 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.core.log.critical(str(e))
+ traceback.print_exc()
+ job.setStatus("failed")
+ job.error = str(e)
+ job.release()
+ return
+
+ if job.plugin.getPluginType() == "hoster":
+ spaceLeft = freeSpace(self.core.config.get("general", "download_folder")) / 1024 / 1024
+ if spaceLeft < self.core.config.get("general", "min_free_space"):
+ self.core.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 = DecrypterThread(self, job)
+ else:
+ thread = 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/pyload/manager/__init__.py b/pyload/manager/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/manager/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/network/Browser.py b/pyload/network/Browser.py
new file mode 100644
index 000000000..77524c26f
--- /dev/null
+++ b/pyload/network/Browser.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+import logging
+
+from pyload.network.HTTPRequest import HTTPRequest
+from pyload.network.HTTPDownload import HTTPDownload
+
+
+class Browser(object):
+ __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl")
+
+
+ def __init__(self, bucket=None, options={}):
+ self.log = logging.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):
+ return self.dl.arrived if self.dl else 0
+
+
+ @property
+ def percent(self):
+ return (self.arrived * 100) / self.size if self.size else 0
+
+
+ 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..4f152f0d4
--- /dev/null
+++ b/pyload/network/Bucket.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import threading
+import time
+
+
+MIN_RATE = 10240 #: 10kb minimum rate
+
+
+class Bucket(object):
+
+ def __init__(self):
+ self.rate = 0 #: bytes per second, maximum targeted throughput
+ self.tokens = 0
+ self.timestamp = time.time()
+ self.lock = threading.Lock()
+
+
+ def __nonzero__(self):
+ return self.rate >= MIN_RATE
+
+
+ def setRate(self, rate):
+ self.lock.acquire()
+ self.rate = int(rate)
+ self.lock.release()
+
+
+ def consumed(self, amount):
+ """ return the time the process has to sleep, after it consumed a specified amount """
+ if self.rate < MIN_RATE:
+ return 0 #: May become unresponsive otherwise
+
+ self.lock.acquire()
+ self.calc_tokens()
+ self.tokens -= amount
+
+ time = -self.tokens / float(self.rate) if self.tokens < 0 else 0
+
+ self.lock.release()
+ return time
+
+
+ def calc_tokens(self):
+ if self.tokens < self.rate:
+ now = time.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..e4d3424d8
--- /dev/null
+++ b/pyload/network/CookieJar.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import Cookie
+import datetime
+import time
+
+
+# monkey patch for 32 bit systems
+def _getdate(future=0, weekdayname=Cookie._weekdayname, monthname=Cookie._monthname):
+ dt = datetime.datetime.now() + datetime.timedelta(seconds=int(future))
+ return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \
+ (weekdayname[dt.weekday()], dt.day, monthname[dt.month], dt.year, dt.hour, dt.minute, dt.second)
+
+Cookie._getdate = _getdate
+
+
+class CookieJar(Cookie.SimpleCookie):
+
+ def getCookie(self, name):
+ return self[name].value
+
+
+ def setCookie(self, domain, name, value, path="/", exp=None, secure="FALSE"):
+ self[name] = value
+ self[name]['domain'] = domain
+ self[name]['path'] = path
+
+ # Value of expires should be integer if possible
+ # otherwise the cookie won't be used
+ if not exp:
+ expires = time.time() + 3600 * 24 * 180
+ else:
+ try:
+ expires = int(exp)
+ except ValueError:
+ expires = exp
+
+ self[name]['expires'] = expires
+
+ if secure == "TRUE":
+ self[name]['secure'] = secure
diff --git a/pyload/network/HTTPChunk.py b/pyload/network/HTTPChunk.py
new file mode 100644
index 000000000..1ce72a918
--- /dev/null
+++ b/pyload/network/HTTPChunk.py
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import codecs
+import os
+import re
+import time
+import urllib
+
+import pycurl
+
+from pyload.network.HTTPRequest import HTTPRequest
+from pyload.utils import fs_encode
+
+
+class WrongFormat(Exception):
+ pass
+
+
+class ChunkInfo(object):
+
+ 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 xrange(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 os.path.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 os.remove(self):
+ fs_name = fs_encode("%s.chunks" % self.name)
+ if os.path.exists(fs_name): os.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 = os.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 = re.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:
+ time.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
+
+ time.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 or "filename*=" in line):
+ if "filename*=" in line:
+ # extended version according to RFC 6266 and RFC 5987.
+ encoding = line.partition("*=")[2].partition("''")[0]
+ name = orgline.partition("''")[2]
+ name = urllib.unquote(str(name)).decode(charEnc(encoding))
+ else:
+ # basic version, US-ASCII only
+ name = orgline.partition("filename=")[2]
+
+ name = name.replace('"', "").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()
+ os.fsync(self.fp.fileno()) #: make sure everything was written to disk
+ self.fp.close() #: needs to be closed, or merging chunks will fail
+
+
+ def os.close(self):
+ """ closes everything, unusable after this """
+ if self.fp: self.fp.close()
+ self.c.close()
+ if hasattr(self, "p"):
+ del self.p
+
+
+def charEnc(enc):
+ return {'utf-8' : "utf_8",
+ 'iso-8859-1': "latin_1"}.get(enc, "unknown")
diff --git a/pyload/network/HTTPDownload.py b/pyload/network/HTTPDownload.py
new file mode 100644
index 000000000..e5917aa1f
--- /dev/null
+++ b/pyload/network/HTTPDownload.py
@@ -0,0 +1,319 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+import logging
+import os
+import shutil
+import time
+
+import pycurl
+
+from pyload.network.HTTPChunk import ChunkInfo, HTTPChunk
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugin.Plugin import Abort
+from pyload.utils import fs_join, fs_encode
+
+
+class HTTPDownload(object):
+ """ loads a url http + ftp """
+
+ def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None,
+ options={}, progress=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 = logging.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 = True
+ self.m = pycurl.CurlMulti()
+
+ # needed for speed calculation
+ self.lastArrived = []
+ self.speeds = []
+ self.lastSpeeds = [0, 0]
+
+ self.progress = progress
+
+
+ @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):
+ return (self.arrived * 100) / self.size if self.size else 0
+
+ def _copyChunks(self):
+ init = fs_encode(self.info.getChunkName(0)) #: initial chunk name
+
+ if self.info.getCount() > 1:
+ with open(init, "rb+") as fo: #: first chunkfile
+ for i in xrange(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))
+ with open(fname, "rb") as fi:
+ buf = 32 * 1024
+ while True: #: copy in chunks, consumes less memory
+ data = fi.read(buf)
+ if not data:
+ break
+ fo.write(data)
+ if fo.tell() < self.info.getChunkRange(i)[1]:
+ reshutil.move(init)
+ self.info.remove() #: there are probably invalid chunks
+ raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.")
+ reshutil.move(fname) #: remove chunk
+
+ if self.nameDisposition and self.disposition:
+ self.filename = fs_join(os.path.dirname(self.filename), self.nameDisposition)
+
+ shutil.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 resume is True and 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.info.save()
+
+ 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() is 0: #: This is a resume, if we were chunked originally assume still can
+ self.chunkSupport = False
+
+ 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 xrange(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.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)
+ reshutil.move(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
+
+ # time.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.progress:
+ self.progress(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 os.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..e450cacfe
--- /dev/null
+++ b/pyload/network/HTTPRequest.py
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+from __future__ import with_statement
+
+import cStringIO
+import codecs
+import httplib
+import logging
+import urllib
+
+import pycurl
+
+from pyload.plugin.Plugin import Abort, Fail
+
+from pyload.utils import encode
+
+
+def myquote(url):
+ return urllib.quote(encode(url), safe="%/:=&?~#+!$,;'@()*[]")
+
+
+def myurlencode(data):
+ data = dict(data)
+ return urllib.urlencode(dict((encode(x), encode(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, httplib.responses[int(code)]))
+ self.code = code
+ self.content = content
+
+
+class HTTPRequest(object):
+
+ def __init__(self, cookies=None, options=None):
+ self.c = pycurl.Curl()
+ self.rep = cStringIO.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 = logging.getLogger("log")
+
+
+ def initHandle(self):
+ """ sets common options to curl handle """
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.c.setopt(pycurl.MAXREDIRS, 10)
+ 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, 60)
+ self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
+ if hasattr(pycurl, "USE_SSL"):
+ self.c.setopt(pycurl.USE_SSL, pycurl.CURLUSESSL_TRY)
+
+ # self.c.setopt(pycurl.VERBOSE, 1)
+
+ self.c.setopt(pycurl.USERAGENT,
+ "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.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 = urllib.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, encode(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, follow_location=True, save_cookies=True):
+ """ load and returns a given page """
+
+ self.setRequestContext(url, get, post, referer, cookies, multipart)
+
+ self.header = ""
+
+ self.c.setopt(pycurl.HTTPHEADER, self.headers)
+
+ if post:
+ self.c.setopt(pycurl.POST, 1)
+ else:
+ self.c.setopt(pycurl.HTTPGET, 1)
+
+ if not follow_location:
+ self.c.setopt(pycurl.FOLLOWLOCATION, 0)
+
+ if just_header:
+ self.c.setopt(pycurl.NOBODY, 1)
+
+ self.c.perform()
+ rep = self.header if just_header else self.getResponse()
+
+ if not follow_location:
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+
+ if just_header:
+ self.c.setopt(pycurl.NOBODY, 0)
+
+ self.c.setopt(pycurl.POSTFIELDS, "")
+ self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL)
+ self.code = self.verifyHeader()
+
+ if save_cookies:
+ 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 ""
+ else:
+ value = self.rep.getvalue()
+ self.rep.close()
+ self.rep = cStringIO.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 codecs.lookup(encoding).name == 'utf-8' and rep.startswith(codecs.BOM_UTF8):
+ encoding = 'utf-8-sig'
+
+ decoder = codecs.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
+
+ with open("response.dump", "wb") as f:
+ f.write(rep)
+ raise Fail("Loaded url exceeded size limit")
+ else:
+ 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/JsEngine.py b/pyload/network/JsEngine.py
new file mode 100644
index 000000000..8b5e2d760
--- /dev/null
+++ b/pyload/network/JsEngine.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+import os
+import subprocess
+import sys
+import urllib
+
+from pyload.utils import encode, decode, uniqify
+
+
+class JsEngine(object):
+ """ JS Engine superclass """
+
+ def __init__(self, core, engine=None):
+ self.core = core
+ self.engine = None #: Engine Instance
+
+ 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=None):
+ """ Convert engine name (string) to relative JSE class (AbstractEngine extended) """
+ if not engine:
+ JSE = self.engine
+
+ elif 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:
+ raise ValueError("JSE")
+
+ elif issubclass(engine, AbstractEngine):
+ JSE = engine
+
+ else:
+ raise TypeError("engine")
+
+ return JSE
+
+
+ def set(self, engine):
+ """ Set engine name (string) or JSE class (AbstractEngine extended) as default engine """
+ if isinstance(engine, basestring):
+ return 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"""
+ JSE = self.get(engine)
+
+ if not JSE:
+ raise TypeError("engine")
+
+ 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(object):
+ """ JSE base class """
+
+ _name = ""
+
+
+ def __init__(self, force=False):
+ self.setup()
+ self.available = force or self.find()
+
+
+ def setup(self):
+ pass
+
+
+ @classmethod
+ def find(cls):
+ """ Check if the engine is available """
+ try:
+ __import__(cls._name)
+ except Exception:
+ try:
+ out, err = cls(True).eval("23+19")
+ except Exception:
+ res = False
+ else:
+ res = out == "42"
+ else:
+ res = True
+
+ return res
+
+
+ def _eval(self, 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(self, 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
+
+ return res
+
+
+class CommonEngine(AbstractEngine):
+
+ _name = "js"
+
+
+ def setup(self):
+ # subprocess.Popen(["js", "-v"], bufsize=-1).communicate()
+ pass
+
+
+ def eval(self, script):
+ script = "print(eval(unescape('%s')))" % urllib.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')))" % urllib.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')))" % urllib.quote(script)
+ args = ["java", "-cp", self.path, "org.mozilla.javascript.tools.shell.Main", "-e", script]
+ res = decode(self._eval(args))
+ try:
+ return res.encode("ISO-8859-1")
+ finally:
+ return res
+
+
+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')))" % urllib.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/network/RequestFactory.py b/pyload/network/RequestFactory.py
new file mode 100644
index 000000000..d10c35cf0
--- /dev/null
+++ b/pyload/network/RequestFactory.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+import threading
+
+from pyload.network.Browser import Browser
+from pyload.network.Bucket import Bucket
+from pyload.network.CookieJar import CookieJar
+from pyload.network.HTTPRequest import HTTPRequest
+from pyload.network.XDCCRequest import XDCCRequest
+
+
+class RequestFactory(object):
+
+ def __init__(self, core):
+ self.lock = threading.Lock()
+ self.core = core
+ self.bucket = Bucket()
+ self.updateBucket()
+ self.cookiejars = {}
+
+
+ def iface(self):
+ return self.core.config.get("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 """
+ cj = None
+
+ if 'cookies' in kwargs:
+ if isinstance(kwargs['cookies'], CookieJar):
+ cj = kwargs['cookies']
+ elif isinstance(kwargs['cookies'], list):
+ cj = CookieJar(None)
+ for cookie in kwargs['cookies']:
+ if isinstance(cookie, tuple) and len(cookie) == 3:
+ cj.setCookie(*cookie)
+
+ h = HTTPRequest(cj, 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.get("proxy", "proxy"):
+ return {}
+ else:
+ type = "http"
+ setting = self.core.config.get("proxy", "type").lower()
+ if setting == "socks4":
+ type = "socks4"
+ elif setting == "socks5":
+ type = "socks5"
+
+ username = None
+ if self.core.config.get("proxy", "username") and self.core.config.get("proxy", "username").lower() != "none":
+ username = self.core.config.get("proxy", "username")
+
+ pw = None
+ if self.core.config.get("proxy", "password") and self.core.config.get("proxy", "password").lower() != "none":
+ pw = self.core.config.get("proxy", "password")
+
+ return {
+ "type": type,
+ "address": self.core.config.get("proxy", "address"),
+ "port": self.core.config.get("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.get("download", "ipv6")}
+
+
+ def updateBucket(self):
+ """ set values in the bucket according to settings"""
+ if not self.core.config.get("download", "limit_speed"):
+ self.bucket.setRate(-1)
+ else:
+ self.bucket.setRate(self.core.config.get("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..470ab96a7
--- /dev/null
+++ b/pyload/network/XDCCRequest.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+# @author: jeix
+
+import os
+import select
+import socket
+import struct
+import time
+
+from pyload.plugin.Plugin import Abort
+
+
+class XDCCRequest(object):
+
+ 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, progress=None):
+
+ ircbuffer = ""
+ lastUpdate = time.time()
+ cumRecvLen = 0
+
+ dccsock = self.createSocket()
+
+ dccsock.settimeout(self.timeout)
+ dccsock.connect((ip, port))
+
+ if os.path.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 os.path.exists(newfilename):
+ filename = newfilename
+ break
+
+ fh = open(filename, "wb")
+
+ # recv loop for dcc socket
+ while True:
+ if self.abort:
+ dccsock.close()
+ fh.close()
+ os.remove(filename)
+ raise Abort
+
+ self._keepAlive(irc, ircbuffer)
+
+ data = dccsock.recv(4096)
+ dataLen = len(data)
+ self.recv += dataLen
+
+ cumRecvLen += dataLen
+
+ now = time.time()
+ timespan = now - lastUpdate
+ if timespan > 1:
+ self.speed = cumRecvLen / timespan
+ cumRecvLen = 0
+ lastUpdate = now
+
+ if progress:
+ progress(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.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):
+ return (self.recv * 100) / self.filesize if elf.filesize else 0
+
+
+ def os.close(self):
+ pass
diff --git a/pyload/network/__init__.py b/pyload/network/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/network/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/Account.py b/pyload/plugin/Account.py
new file mode 100644
index 000000000..e4bfd76e8
--- /dev/null
+++ b/pyload/plugin/Account.py
@@ -0,0 +1,309 @@
+# -*- coding: utf-8 -*-
+
+import random
+import threading
+import time
+import traceback
+
+from pyload.plugin.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.03"
+
+ __description = """Base account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "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 = threading.RLock()
+ self.timestamps = {}
+
+ self.init()
+
+ self.setAccounts(accounts)
+
+
+ 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.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:
+ traceback.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)}
+ traceback.print_exc()
+
+ if req:
+ req.close()
+
+ self.logDebug("Account Info: %s" % infos)
+
+ infos['timestamp'] = time.time()
+ self.infos[name] = infos
+ elif "timestamp" in self.infos[name] and self.infos[name]['timestamp'] + self.info_threshold * 60 < time.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'], #: commented due security reason
+ "options" : self.accounts[name]['options'],
+ "valid" : self.accounts[name]['valid'],
+ "trafficleft": None, #: in bytes, -1 for unlimited
+ "maxtraffic" : None,
+ "premium" : None,
+ "timestamp" : 0, #: time this info was retrieved
+ "type" : self.getClassName()}
+
+
+ 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.getClassName(), 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.getClassName(), 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.time(start.split(":"), end.split(":")):
+ continue
+ except Exception:
+ 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.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 random.choice(usable)
+
+
+ def canUse(self):
+ return self.selectAccount() != (None, None)
+
+
+ def parseTraffic(self, value, unit=None): #: return bytes
+ if not unit and not isinstance(value, basestring):
+ unit = "KB"
+ return parseFileSize(value, unit)
+
+
+ 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.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.time():
+ self.logDebug("Reached login timeout for %s" % user)
+ return self.relogin(user)
+ else:
+ return True
+ else:
+ return False
diff --git a/pyload/plugin/Addon.py b/pyload/plugin/Addon.py
new file mode 100644
index 000000000..cf3397002
--- /dev/null
+++ b/pyload/plugin/Addon.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+
+import traceback
+
+from pyload.plugin.Plugin import Base
+from pyload.utils import has_method
+
+
+class Expose(object):
+ """ used for decoration to declare rpc services """
+
+ def __new__(cls, f, *args, **kwargs):
+ addonManager.addRPC(f.__module__, f.func_name, f.func_doc)
+ return f
+
+
+def threaded(fn):
+
+ def run(*args, **kwargs):
+ addonManager.startThread(fn, *args, **kwargs)
+
+ return run
+
+
+class Addon(Base):
+ __name = "Addon"
+ __type = "addon"
+ __version = "0.01"
+
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base addon plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de"),
+ ("RaNaN", "RaNaN@pyload.org")]
+
+
+ #: automatically register event listeners for functions, attribute will be deleted dont use it yourself
+ event_map = {}
+
+ # Deprecated alternative to event_map
+ #: List of events the plugin can handle, name the functions exactly like eventname.
+ event_list = [] #@NOTE: dont make duplicate entries in event_map
+
+
+ def __init__(self, core, manager):
+ Base.__init__(self, core)
+
+ #: Provide information in dict here, usable by API `getInfo`
+ self.info = {}
+
+ #: Callback of periodical job task, used by AddonManager
+ self.cb = None
+ self.interval = 60
+
+ #: `AddonManager`
+ 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:
+ self.logWarning(_("Plugin used deprecated `event_list`, use `event_map` instead"))
+
+ for f in self.event_list:
+ self.manager.addEvent(f, getattr(self, f))
+
+ self.event_list = None
+
+ self.setup()
+
+
+ def initPeriodical(self, delay=0, threaded=False):
+ self.cb = self.core.scheduler.addJob(max(0, delay), self._periodical, [threaded], threaded=threaded)
+
+
+ def _periodical(self, threaded):
+ if self.interval < 0:
+ self.cb = None
+ return
+
+ try:
+ self.periodical()
+
+ except Exception, e:
+ self.logError(_("Error executing addon: %s") % e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ self.cb = self.core.scheduler.addJob(self.interval, self._periodical, [threaded], threaded=threaded)
+
+
+ def __repr__(self):
+ return "<Addon %s>" % self.getClassName()
+
+
+ def setup(self):
+ """ more init stuff if needed """
+ pass
+
+
+ def deactivate(self):
+ """ called when addon was deactivated """
+ if has_method(self.__class__, "unload"):
+ self.logWarning(_("Deprecated method `unload`, use `deactivate` instead"))
+ self.unload()
+
+
+ def unload(self): #: Deprecated, use method `deactivate` instead
+ pass
+
+
+ def isActivated(self):
+ """ checks if addon is activated"""
+ return self.getConfig("activated")
+
+
+ # Event methods - overwrite these if needed
+
+
+ def activate(self):
+ """ called when addon was activated """
+ if has_method(self.__class__, "coreReady"):
+ self.logWarning(_("Deprecated method `coreReady`, use `activate` instead"))
+ self.coreReady()
+
+
+ def coreReady(self): #: Deprecated, use method `activate` instead
+ pass
+
+
+ def exit(self):
+ """ called by core.shutdown just before pyLoad exit """
+ if has_method(self.__class__, "coreExiting"):
+ self.coreExiting()
+
+
+ def coreExiting(self): #: Deprecated, use method `exit` instead
+ 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 captchaTask(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/plugin/Captcha.py b/pyload/plugin/Captcha.py
new file mode 100644
index 000000000..3bf64eb19
--- /dev/null
+++ b/pyload/plugin/Captcha.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Plugin import Base
+
+
+#@TODO: Extend Plugin class; remove all `html` args
+class Captcha(Base):
+ __name = "Captcha"
+ __type = "captcha"
+ __version = "0.25"
+
+ __description = """Base captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def __init__(self, plugin):
+ self.plugin = plugin
+ self.key = None #: last key detected
+ super(Captcha, self).__init__(plugin.core)
+
+
+ def detect_key(self, html=None):
+ raise NotImplementedError
+
+
+ def challenge(self, key=None, html=None):
+ raise NotImplementedError
+
+
+ def result(self, server, challenge):
+ raise NotImplementedError
diff --git a/pyload/plugin/Container.py b/pyload/plugin/Container.py
new file mode 100644
index 000000000..5d7a99eac
--- /dev/null
+++ b/pyload/plugin/Container.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class Container(Crypter):
+ __name = "Container"
+ __type = "container"
+ __version = "0.01"
+
+ __pattern = r'^unmatchable$'
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "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 = fs_join(self.core.config.get("general", "download_folder"), self.pyfile.name)
+ try:
+ with open(self.pyfile.url, "wb") as f:
+ f.write(content)
+ except IOError, e:
+ self.fail(str(e))
+
+ else:
+ self.pyfile.name = os.path.basename(self.pyfile.url)
+ if not os.path.exists(self.pyfile.url):
+ if os.path.exists(fs_join(pypath, self.pyfile.url)):
+ self.pyfile.url = fs_join(pypath, self.pyfile.url)
+ else:
+ self.fail(_("File not exists"))
+
+
+ def deleteTmp(self):
+ if self.pyfile.name.startswith("tmp_"):
+ os.remove(self.pyfile.url)
diff --git a/pyload/plugin/Crypter.py b/pyload/plugin/Crypter.py
new file mode 100644
index 000000000..22afe74d8
--- /dev/null
+++ b/pyload/plugin/Crypter.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+import urlparse
+
+from pyload.plugin.Plugin import Plugin
+from pyload.utils import decode, safe_filename
+
+
+class Crypter(Plugin):
+ __name = "Crypter"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder", "bool", "Save package to subfolder", True), #: Overrides core.config.get("general", "folder_per_package")
+ ("subfolder_per_package", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Base decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ html = None #: last html loaded
+
+
+ def __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 = []
+
+ Plugin.__init__(self, pyfile)
+
+
+ def process(self, pyfile):
+ """ main method """
+
+ self.decrypt(pyfile)
+
+ if self.urls:
+ self.generatePackages()
+
+ elif not self.packages:
+ self.error(_("No link grabbed"), "decrypt")
+
+ self.createPackages()
+
+
+ def decrypt(self, pyfile):
+ raise NotImplementedError
+
+
+ def generatePackages(self):
+ """ generate new packages from self.urls """
+
+ packages = map(lambda name, links: (name, links, None), self.core.api.generatePackages(self.urls).iteritems())
+ self.packages.extend(packages)
+
+
+ def createPackages(self):
+ """ create new packages from self.packages """
+
+ package_folder = self.pyfile.package().folder
+ package_password = self.pyfile.package().password
+ package_queue = self.pyfile.package().queue
+
+ folder_per_package = self.core.config.get("general", "folder_per_package")
+ try:
+ use_subfolder = self.getConfig('use_subfolder')
+ except Exception:
+ use_subfolder = folder_per_package
+ try:
+ subfolder_per_package = self.getConfig('subfolder_per_package')
+ except Exception:
+ subfolder_per_package = True
+
+ for pack in self.packages:
+ name, links, folder = pack
+
+ self.logDebug("Parsed package: %s" % name,
+ "%d links" % len(links),
+ "Saved to folder: %s" % folder if folder else "Saved to download folder")
+
+ links = map(decode, links)
+
+ pid = self.core.api.addPackage(name, links, package_queue)
+
+ if package_password:
+ self.core.api.setPackageData(pid, {"password": package_password})
+
+ setFolder = lambda x: self.core.api.setPackageData(pid, {"folder": x or ""}) #: Workaround to do not break API addPackage method
+
+ if use_subfolder:
+ if not subfolder_per_package:
+ setFolder(package_folder)
+ self.logDebug("Set package %(name)s folder to: %(folder)s" % {"name": name, "folder": folder})
+
+ elif not folder_per_package or name != folder:
+ if not folder:
+ folder = urlparse.urlparse(name).path.split("/")[-1]
+
+ setFolder(safe_filename(folder))
+ self.logDebug("Set package %(name)s folder to: %(folder)s" % {"name": name, "folder": folder})
+
+ elif folder_per_package:
+ setFolder(None)
diff --git a/pyload/plugin/Extractor.py b/pyload/plugin/Extractor.py
new file mode 100644
index 000000000..3e497fc78
--- /dev/null
+++ b/pyload/plugin/Extractor.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+
+from pyload.Datatype import PyFile
+from pyload.plugin.Plugin import Base
+
+
+class ArchiveError(Exception):
+ pass
+
+
+class CRCError(Exception):
+ pass
+
+
+class PasswordError(Exception):
+ pass
+
+
+class Extractor:
+ __name = "Extractor"
+ __type = "extractor"
+ __version = "0.24"
+
+ __description = """Base extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("Immenz" , "immenz@gmx.net")]
+
+
+ EXTENSIONS = []
+ NAME = __name__
+ VERSION = ""
+ REPAIR = False
+
+
+ @classmethod
+ def isArchive(cls, filename):
+ name = os.path.basename(filename).lower()
+ return any(name.endswith(ext) for ext in cls.EXTENSIONS)
+
+
+ @classmethod
+ def isMultipart(cls, filename):
+ return False
+
+
+ @classmethod
+ def isUsable(cls):
+ """ Check if system statisfy dependencies
+ :return: boolean
+ """
+ return None
+
+
+ @classmethod
+ def getTargets(cls, files_ids):
+ """ Filter suited targets from list of filename id tuple list
+ :param files_ids: List of filepathes
+ :return: List of targets, id tuple list
+ """
+ targets = []
+ processed = []
+
+ for fname, id, fout in files_ids:
+ if cls.isArchive(fname):
+ pname = re.sub(cls.re_multipart, '', fname) if cls.isMultipart(fname) else os.path.splitext(fname)[0]
+ if pname not in processed:
+ processed.append(pname)
+ targets.append((fname, id, fout))
+ return targets
+
+
+ def __init__(self, manager, filename, out,
+ fullpath=True,
+ overwrite=False,
+ excludefiles=[],
+ renice=0,
+ delete='No',
+ keepbroken=False,
+ fid=None):
+ """ Initialize extractor for specific file """
+ self.manager = manager
+ self.filename = filename
+ self.out = out
+ self.fullpath = fullpath
+ self.overwrite = overwrite
+ self.excludefiles = excludefiles
+ self.renice = renice
+ self.delete = delete
+ self.keepbroken = keepbroken
+ self.files = [] #: Store extracted files here
+
+ pyfile = self.manager.core.files.getFile(fid) if fid else None
+ self.notifyProgress = lambda x: pyfile.setProgress(x) if pyfile else lambda x: None
+
+
+ def init(self):
+ """ Initialize additional data structures """
+ pass
+
+
+ def check(self):
+ """Quick Check by listing content of archive.
+ Raises error if password is needed, integrity is questionable or else.
+
+ :raises PasswordError
+ :raises CRCError
+ :raises ArchiveError
+ """
+ raise NotImplementedError
+
+
+ def verify(self):
+ """Testing with Extractors buildt-in method
+ Raises error if password is needed, integrity is questionable or else.
+
+ :raises PasswordError
+ :raises CRCError
+ :raises ArchiveError
+ """
+ raise NotImplementedError
+
+
+ def repair(self):
+ return None
+
+
+ def extract(self, password=None):
+ """Extract the archive. Raise specific errors in case of failure.
+
+ :param progress: Progress function, call this to update status
+ :param password password to use
+ :raises PasswordError
+ :raises CRCError
+ :raises ArchiveError
+ :return:
+ """
+ raise NotImplementedError
+
+
+ def getDeleteFiles(self):
+ """Return list of files to delete, do *not* delete them here.
+
+ :return: List with paths of files to delete
+ """
+ return [self.filename]
+
+
+ def list(self, password=None):
+ """Populate self.files at some point while extracting"""
+ return self.files
diff --git a/pyload/plugin/Hook.py b/pyload/plugin/Hook.py
new file mode 100644
index 000000000..ccd4d635b
--- /dev/null
+++ b/pyload/plugin/Hook.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Addon import Addon, threaded
+
+
+class Hook(Addon):
+ __name = "Hook"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/Hoster.py b/pyload/plugin/Hoster.py
new file mode 100644
index 000000000..64c635c45
--- /dev/null
+++ b/pyload/plugin/Hoster.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Plugin import Plugin
+
+
+def getInfo(self):
+ # result = [ .. (name, size, status, url) .. ]
+ return
+
+
+class Hoster(Plugin):
+ __name = "Hoster"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'^unmatchable$'
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/OCR.py b/pyload/plugin/OCR.py
new file mode 100644
index 000000000..126283f01
--- /dev/null
+++ b/pyload/plugin/OCR.py
@@ -0,0 +1,322 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+try:
+ from PIL import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
+
+except ImportError:
+ import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin
+
+import logging
+import os
+import subprocess
+# import tempfile
+
+from pyload.plugin.Plugin import Base
+from pyload.utils import fs_join
+
+
+class OCR(Base):
+ __name = "OCR"
+ __type = "ocr"
+ __version = "0.12"
+
+ __description = """OCR base plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "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 deactivate(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, pagesegmode=None):
+ # tmpTif = tempfile.NamedTemporaryFile(suffix=".tif")
+ try:
+ tmpTif = open(fs_join("tmp", "tmpTif_%s.tif" % self.getClassName()), "wb")
+ tmpTif.close()
+
+ # tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
+ tmpTxt = open(fs_join("tmp", "tmpTxt_%s.txt" % self.getClassName()), "wb")
+ tmpTxt.close()
+
+ except IOError, e:
+ self.logError(e)
+ return
+
+ self.logger.debug("save tiff")
+ self.image.save(tmpTif.name, 'TIFF')
+
+ if os.name == "nt":
+ tessparams = [os.path.join(pypath, "tesseract", "tesseract.exe")]
+ else:
+ tessparams = ["tesseract"]
+
+ tessparams.extend([os.path.abspath(tmpTif.name), os.path.abspath(tmpTxt.name).replace(".txt", "")])
+
+ if pagesegmode:
+ tessparams.extend(["-psm", str(pagesegmode)])
+
+ if subset and (digits or lowercase or uppercase):
+ # tmpSub = tempfile.NamedTemporaryFile(suffix=".subset")
+ with open(fs_join("tmp", "tmpSub_%s.subset" % self.getClassName()), "wb") as tmpSub:
+ 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(os.path.abspath(tmpSub.name))
+
+ 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 Exception:
+ self.result_captcha = ""
+
+ self.logger.debug(self.result_captcha)
+ try:
+ os.remove(tmpTif.name)
+ os.remove(tmpTxt.name)
+ if subset and (digits or lowercase or uppercase):
+ os.remove(tmpSub.name)
+ except Exception:
+ 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 Exception:
+ 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/plugin/Plugin.py b/pyload/plugin/Plugin.py
new file mode 100644
index 000000000..6648c574f
--- /dev/null
+++ b/pyload/plugin/Plugin.py
@@ -0,0 +1,772 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import traceback
+
+import itertools
+import os
+import random
+import re
+import time
+import urllib
+import urlparse
+
+if os.name != "nt":
+ from pwd import getpwnam
+ from grp import getgrnam
+
+from pyload.utils import fs_decode, fs_encode, safe_filename, fs_join, encode
+
+
+def chunks(iterable, size):
+ it = iter(iterable)
+ item = list(itertools.islice(it, size))
+ while item:
+ yield item
+ item = list(itertools.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
+
+
+ def _log(self, type, args):
+ msg = " | ".join([encode(str(a)).strip() for a in args if a])
+ logger = getattr(self.core.log, type)
+ logger("%s: %s" % (self.getClassName(), msg or _("%s MARK" % type.upper())))
+
+
+ def logDebug(self, *args):
+ if self.core.debug:
+ return self._log("debug", args)
+
+
+ def logInfo(self, *args):
+ return self._log("info", args)
+
+
+ def logWarning(self, *args):
+ return self._log("warning", args)
+
+
+ def logError(self, *args):
+ return self._log("error", args)
+
+
+ def logCritical(self, *args):
+ return self._log("critical", args)
+
+
+ def getPluginType(self):
+ return getattr(self, "_%s__type" % self.getClassName())
+
+
+ @classmethod
+ def getClassName(cls):
+ return cls.__name__
+
+
+ def getPluginConfSection(self):
+ return "%s_%s" % (self.getClassName(), getattr(self, "_%s__type" % self.getClassName()))
+
+ #: Deprecated method
+
+
+ def setConf(self, option, value):
+ """ see `setConfig` """
+ self.setConfig(option, value)
+
+
+ def setConfig(self, option, value):
+ """ Set config value for current plugin
+
+ :param option:
+ :param value:
+ :return:
+ """
+ self.core.config.setPlugin(self.getPluginConfSection(), option, value)
+
+ #: Deprecated method
+
+
+ def getConf(self, option):
+ """ see `getConfig` """
+ return self.core.config.getPlugin(self.getPluginConfSection(), option)
+
+
+ def getConfig(self, option):
+ """ Returns config value for current plugin
+
+ :param option:
+ :return:
+ """
+ return self.core.config.getPlugin(self.getPluginConfSection(), option)
+
+
+ def setStorage(self, key, value):
+ """ Saves a value persistently to the database """
+ self.core.db.setStorage(self.getPluginConfSection(), key, value)
+
+
+ def store(self, key, value):
+ """ same as `setStorage` """
+ self.core.db.setStorage(self.getPluginConfSection(), key, value)
+
+
+ def getStorage(self, key=None, default=None):
+ """ Retrieves saved value or dict of all saved entries if key is None """
+ if key:
+ return self.core.db.getStorage(self.getPluginConfSection(), key) or default
+ return self.core.db.getStorage(self.getPluginConfSection(), 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.getClassName(), key)
+
+
+class Plugin(Base):
+ """
+ Base plugin for hoster/crypter.
+ Overwrite `process` / `decrypt` in your subclassed plugin.
+ """
+ __name = "Plugin"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'^unmatchable$'
+ __config = [] #: [("name", "type", "desc", "default")]
+
+ __description = """Base plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de")]
+
+ info = {} #: file info dict
+
+
+ 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.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.getClassName())
+
+ #: 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.getClassName())
+
+ #: 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
+
+ self.html = None #@TODO: Move to hoster class in 0.4.10
+ self.retries = 0
+
+ self.init()
+
+
+ def getChunkCount(self):
+ if self.chunkLimit <= 0:
+ return self.core.config.get("download", "chunks")
+ return min(self.core.config.get("download", "chunks"), self.chunkLimit)
+
+
+ def __call__(self):
+ return self.getClassName()
+
+
+ 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.getClassName())
+ 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 addon
+
+ return True, 10
+
+
+ def setReconnect(self, reconnect):
+ reconnect = bool(reconnect)
+ self.logDebug("Set wantReconnect to: %s (previous: %s)" % (reconnect, self.wantReconnect))
+ self.wantReconnect = reconnect
+
+
+ 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
+ """
+ wait_time = int(seconds) + 1
+ wait_until = time.time() + wait_time
+
+ self.logDebug("Set waitUntil to: %f (previous: %f)" % (wait_until, self.pyfile.waitUntil),
+ "Wait: %d seconds" % wait_time)
+
+ self.pyfile.waitUntil = wait_until
+
+ if reconnect is not None:
+ self.setReconnect(reconnect)
+
+
+ def wait(self, seconds=None, reconnect=None):
+ """ waits the time previously set """
+
+ pyfile = self.pyfile
+
+ if seconds is not None:
+ self.setWait(seconds)
+
+ if reconnect is not None:
+ self.setReconnect(reconnect)
+
+ self.waiting = True
+
+ status = pyfile.status
+ pyfile.setStatus("waiting")
+
+ self.logInfo(_("Wait: %d seconds") % (pyfile.waitUntil - time.time()),
+ _("Reconnect: %s") % self.wantReconnect)
+
+ if self.account:
+ self.logDebug("Ignore reconnection due account logged")
+
+ while pyfile.waitUntil > time.time():
+ if pyfile.abort:
+ self.abort()
+
+ time.sleep(1)
+ else:
+ while pyfile.waitUntil > time.time():
+ self.thread.m.reconnecting.wait(2)
+
+ if pyfile.abort:
+ self.abort()
+
+ if self.thread.m.reconnecting.isSet():
+ self.waiting = False
+ self.wantReconnect = False
+ raise Reconnect
+
+ time.sleep(1)
+
+ self.waiting = False
+
+ pyfile.status = status
+
+
+ def fail(self, reason):
+ """ fail and give reason """
+ raise Fail(reason)
+
+
+ def abort(self, reason=""):
+ """ abort and give reason """
+ if reason:
+ self.pyfile.error = str(reason)
+ raise Abort
+
+
+ def error(self, reason="", type=""):
+ if not reason and not type:
+ type = "unknown"
+
+ msg = _("%s error") % _(type.strip().capitalize()) if type else _("Error")
+ msg += ": " + reason.strip() if reason else ""
+ msg += _(" | Plugin may be out of date")
+
+ raise Fail(msg)
+
+
+ def offline(self, reason=""):
+ """ fail and indicate file is offline """
+ if reason:
+ self.pyfile.error = str(reason)
+ raise Fail("offline")
+
+
+ def tempOffline(self, reason=""):
+ """ fail and indicates file ist temporary offline, the core may take consequences """
+ if reason:
+ self.pyfile.error = str(reason)
+ raise Fail("temp. offline")
+
+
+ def retry(self, max_tries=5, 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:
+ self.error(reason or _("Max retries reached"), "retry")
+
+ self.wait(wait_time, False)
+
+ self.retries += 1
+ raise Retry(reason)
+
+
+ def invalidCaptcha(self):
+ self.logError(_("Invalid captcha"))
+ if self.cTask:
+ self.cTask.invalid()
+
+
+ def correctCaptcha(self):
+ self.logInfo(_("Correct captcha"))
+ if self.cTask:
+ self.cTask.correct()
+
+
+ def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg',
+ result_type='textual', timeout=290):
+ """ 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.time())[-6:].replace(".", "")
+
+ with open(os.path.join("tmp", "tmpCaptcha_%s_%s.%s" % (self.getClassName(), id, imgtype)), "wb") as tmpCaptcha:
+ tmpCaptcha.write(img)
+
+ has_plugin = self.getClassName() in self.core.pluginManager.ocrPlugins
+
+ if self.core.captcha:
+ Ocr = self.core.pluginManager.loadClass("ocr", self.getClassName())
+ else:
+ Ocr = None
+
+ if Ocr and not forceUser:
+ time.sleep(random.randint(3000, 5000) / 1000.0)
+ if self.pyfile.abort:
+ self.abort()
+
+ ocr = Ocr()
+ result = ocr.get_captcha(tmpCaptcha.name)
+ else:
+ captchaManager = self.core.captchaManager
+ task = captchaManager.newTask(img, imgtype, tmpCaptcha.name, result_type)
+ self.cTask = task
+ captchaManager.handleCaptcha(task, timeout)
+
+ while task.isWaiting():
+ if self.pyfile.abort:
+ captchaManager.removeTask(task)
+ self.abort()
+ time.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" % result)
+
+ if not self.core.debug:
+ try:
+ os.remove(tmpCaptcha.name)
+ except Exception:
+ pass
+
+ return result
+
+
+ def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False, follow_location=True, save_cookies=True):
+ """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
+ :param follow_location: If True follow location else not
+ :param save_cookies: If True saves received cookies else discard them
+ :return: Loaded content
+ """
+ if self.pyfile.abort:
+ self.abort()
+
+ if not url:
+ self.fail(_("No url given"))
+
+ url = urllib.unquote(encode(url).strip()) #@NOTE: utf8 vs decode -> please use decode attribute in all future plugins
+
+ if self.core.debug:
+ self.logDebug("Load url: " + url, *["%s=%s" % (key, val) for key, val in locals().iteritems() if key not in ("self", "url")])
+
+ res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode, follow_location=follow_location, save_cookies=save_cookies)
+
+ if decode:
+ res = encode(res)
+
+ if self.core.debug:
+ from inspect import currentframe
+
+ frame = currentframe()
+ framefile = fs_join("tmp", self.getClassName(), "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno))
+ try:
+ if not os.path.exists(os.path.join("tmp", self.getClassName())):
+ os.makedirs(os.path.join("tmp", self.getClassName()))
+
+ with open(framefile, "wb") as f:
+ del frame #: delete the frame or it wont be cleaned
+ f.write(res)
+ except IOError, e:
+ self.logError(e)
+
+ 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.strip().lower()
+ 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
+ """
+ if self.pyfile.abort:
+ self.abort()
+
+ if not url:
+ self.fail(_("No url given"))
+
+ url = urllib.unquote(encode(url).strip())
+
+ if self.core.debug:
+ self.logDebug("Download url: " + url, *["%s=%s" % (key, val) for key, val in locals().iteritems() if key not in ("self", "url")])
+
+ self.checkForSameFiles()
+
+ self.pyfile.setStatus("downloading")
+
+ if disposition:
+ self.pyfile.name = urlparse.urlparse(url).path.split('/')[-1] or self.pyfile.name
+
+ download_folder = self.core.config.get("general", "download_folder")
+
+ location = fs_join(download_folder, self.pyfile.package().folder)
+
+ if not os.path.exists(location):
+ try:
+ os.makedirs(location, int(self.core.config.get("permission", "folder"), 8))
+
+ if self.core.config.get("permission", "change_dl") and os.name != "nt":
+ uid = getpwnam(self.core.config.get("permission", "user"))[2]
+ gid = getgrnam(self.core.config.get("permission", "group"))[2]
+ os.chown(location, uid, gid)
+
+ except Exception, e:
+ self.fail(e)
+
+ # convert back to unicode
+ location = fs_decode(location)
+ name = safe_filename(self.pyfile.name)
+
+ filename = os.path.join(location, name)
+
+ self.core.addonManager.dispatchEvent("download-start", 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 newname:
+ newname = urlparse.urlparse(newname).path.split('/')[-1]
+
+ if disposition and newname != name:
+ self.logInfo(_("%(name)s saved as %(newname)s") % {"name": name, "newname": newname})
+ self.pyfile.name = newname
+ filename = os.path.join(location, newname)
+
+ fs_filename = fs_encode(filename)
+
+ if self.core.config.get("permission", "change_file"):
+ try:
+ os.chmod(fs_filename, int(self.core.config.get("permission", "file"), 8))
+ except Exception, e:
+ self.logWarning(_("Setting file mode failed"), e)
+
+ if self.core.config.get("permission", "change_dl") and os.name != "nt":
+ try:
+ uid = getpwnam(self.core.config.get("permission", "user"))[2]
+ gid = getgrnam(self.core.config.get("permission", "group"))[2]
+ os.chown(fs_filename, uid, gid)
+
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed"), 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 os.path.exists(lastDownload):
+ return None
+
+ size = os.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")
+
+ with open(lastDownload, "rb") as f:
+ content = f.read(read_size if read_size else -1)
+
+ # produces encoding errors, better log to other file in the future?
+ # self.logDebug("Content: %s" % content)
+ for name, rule in rules.iteritems():
+ if isinstance(rule, basestring):
+ if rule in content:
+ if delete:
+ os.remove(lastDownload)
+ return name
+ elif hasattr(rule, "search"):
+ m = rule.search(content)
+ if m:
+ if delete:
+ os.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.core.config.get("general", "download_folder")
+ location = fs_join(download_folder, pack.folder, self.pyfile.name)
+
+ if starting and self.core.config.get("download", "skip_existing") and os.path.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 os.path.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/plugin/__init__.py b/pyload/plugin/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/account/AlldebridCom.py b/pyload/plugin/account/AlldebridCom.py
new file mode 100644
index 000000000..6ba7a80f9
--- /dev/null
+++ b/pyload/plugin/account/AlldebridCom.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import xml.dom.minidom as dom
+
+import BeautifulSoup
+
+from pyload.plugin.Account import Account
+
+
+class AlldebridCom(Account):
+ __name = "AlldebridCom"
+ __type = "account"
+ __version = "0.23"
+
+ __description = """AllDebrid.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ html = req.load("http://www.alldebrid.com/account/")
+ soup = BeautifulSoup.BeautifulSoup(html)
+
+ # 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.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 Exception:
+ data = self.getAccountData(user)
+ html = req.load("http://www.alldebrid.com/api.php",
+ get={'action': "info_user", 'login': user, 'pw': data['password']})
+
+ self.logDebug(html)
+
+ xml = dom.parseString(html)
+ exp_time = 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):
+ html = req.load("http://www.alldebrid.com/register/",
+ get={'action' : "login",
+ 'login_login' : user,
+ 'login_password': data['password']},
+ decode=True)
+
+ if "This login doesn't exist" in html \
+ or "The password is not valid" in html \
+ or "Invalid captcha" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/BackinNet.py b/pyload/plugin/account/BackinNet.py
new file mode 100644
index 000000000..79d46c761
--- /dev/null
+++ b/pyload/plugin/account/BackinNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class BackinNet(XFSAccount):
+ __name = "BackinNet"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Backin.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "backin.net"
diff --git a/pyload/plugin/account/BillionuploadsCom.py b/pyload/plugin/account/BillionuploadsCom.py
new file mode 100644
index 000000000..982a2cc34
--- /dev/null
+++ b/pyload/plugin/account/BillionuploadsCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class BillionuploadsCom(XFSAccount):
+ __name = "BillionuploadsCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Billionuploads.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "billionuploads.com"
diff --git a/pyload/plugin/account/BitshareCom.py b/pyload/plugin/account/BitshareCom.py
new file mode 100644
index 000000000..8a64d3b4c
--- /dev/null
+++ b/pyload/plugin/account/BitshareCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class BitshareCom(Account):
+ __name = "BitshareCom"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Bitshare account plugin"""
+ __license = "GPLv3"
+ __authors = [("Paul King", "")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://bitshare.com/mysettings.html")
+
+ if "\"http://bitshare.com/myupgrade.html\">Free" in html:
+ return {"validuntil": -1, "trafficleft": -1, "premium": False}
+
+ if not '<input type="checkbox" name="directdownload" checked="checked" />' in html:
+ self.logWarning(_("Activate direct Download in your Bitshare Account"))
+
+ return {"validuntil": -1, "trafficleft": -1, "premium": True}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://bitshare.com/login.html",
+ post={"user": user, "password": data['password'], "submit": "Login"},
+ decode=True)
+
+ if "login" in req.lastEffectiveURL:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/CatShareNet.py b/pyload/plugin/account/CatShareNet.py
new file mode 100644
index 000000000..8f821413f
--- /dev/null
+++ b/pyload/plugin/account/CatShareNet.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class CatShareNet(Account):
+ __name = "CatShareNet"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """CatShareNet account plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", "")]
+
+
+ PREMIUM_PATTERN = r'<a href="/premium">Konto:[\s\n]*Premium'
+ VALID_UNTIL_PATTERN = r'>Konto premium.*?<strong>(.*?)</strong></span>'
+ TRAFFIC_LEFT_PATTERN = r'<a href="/premium">([0-9.]+ [kMG]B)'
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = -1
+ trafficleft = -1
+
+ html = req.load("http://catshare.net/", decode=True)
+
+ if re.search(self.PREMIUM_PATTERN, html):
+ premium = True
+
+ try:
+ expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
+ self.logDebug("Expire date: " + expiredate)
+
+ validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
+
+ except Exception:
+ pass
+
+ try:
+ trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
+
+ except Exception:
+ pass
+
+ return {'premium': premium, 'trafficleft': trafficleft, 'validuntil': validuntil}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://catshare.net/login",
+ post={'user_email': user,
+ 'user_password': data['password'],
+ 'remindPassword': 0,
+ 'user[submit]': "Login"},
+ decode=True)
+
+ if not '<a href="/logout">Wyloguj</a>' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/CloudzillaTo.py b/pyload/plugin/account/CloudzillaTo.py
new file mode 100644
index 000000000..bee7c5a17
--- /dev/null
+++ b/pyload/plugin/account/CloudzillaTo.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class CloudzillaTo(Account):
+ __name = "CloudzillaTo"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Cloudzilla.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ PREMIUM_PATTERN = r'<h2>account type</h2>\s*Premium Account'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.cloudzilla.to/")
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is not None
+
+ return {'validuntil': -1, 'trafficleft': -1, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.cloudzilla.to/",
+ post={'lusername': user,
+ 'lpassword': data['password'],
+ 'w' : "dologin"},
+ decode=True)
+
+ if "ERROR" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/CramitIn.py b/pyload/plugin/account/CramitIn.py
new file mode 100644
index 000000000..ccd291776
--- /dev/null
+++ b/pyload/plugin/account/CramitIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class CramitIn(XFSAccount):
+ __name = "CramitIn"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Cramit.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "cramit.in"
diff --git a/pyload/plugin/account/CzshareCom.py b/pyload/plugin/account/CzshareCom.py
new file mode 100644
index 000000000..4913ed88f
--- /dev/null
+++ b/pyload/plugin/account/CzshareCom.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class CzshareCom(Account):
+ __name = "CzshareCom"
+ __type = "account"
+ __version = "0.18"
+
+ __description = """Czshare.com account plugin, now Sdilej.cz"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ CREDIT_LEFT_PATTERN = r'<tr class="active">\s*<td>([\d ,]+) (KiB|MiB|GiB)</td>\s*<td>([^<]*)</td>\s*</tr>'
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = None
+ trafficleft = None
+
+ html = req.load("http://sdilej.cz/prehled_kreditu/")
+
+ try:
+ m = re.search(self.CREDIT_LEFT_PATTERN, html)
+ trafficleft = self.parseTraffic(m.group(1).replace(' ', '').replace(',', '.')) + m.group(2)
+ validuntil = time.mktime(time.strptime(m.group(3), '%d.%m.%y %H:%M'))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ premium = True
+
+ return {'premium' : premium,
+ 'validuntil' : validuntil,
+ 'trafficleft': trafficleft}
+
+
+ def login(self, user, data, req):
+ html = req.load('https://sdilej.cz/index.php',
+ post={"Prihlasit": "Prihlasit",
+ "login-password": data['password'],
+ "login-name": user},
+ decode=True)
+
+ if '<div class="login' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/DebridItaliaCom.py b/pyload/plugin/account/DebridItaliaCom.py
new file mode 100644
index 000000000..49959d5c3
--- /dev/null
+++ b/pyload/plugin/account/DebridItaliaCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class DebridItaliaCom(Account):
+ __name = "DebridItaliaCom"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Debriditalia.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ WALID_UNTIL_PATTERN = r'Premium valid till: (.+?) \|'
+
+
+ def loadAccountInfo(self, user, req):
+ info = {'premium': False, 'validuntil': None, 'trafficleft': None}
+ html = req.load("http://debriditalia.com/")
+
+ if 'Account premium not activated' not in html:
+ m = re.search(self.WALID_UNTIL_PATTERN, html)
+ if m:
+ validuntil = time.mktime(time.strptime(m.group(1), "%d/%m/%Y %H:%M"))
+ info = {'premium': True, 'validuntil': validuntil, 'trafficleft': -1}
+ else:
+ self.logError(_("Unable to retrieve account information"))
+
+ return info
+
+
+ def login(self, user, data, req):
+ html = req.load("http://debriditalia.com/login.php",
+ get={'u': user, 'p': data['password']},
+ decode=True)
+
+ if 'NO' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/DepositfilesCom.py b/pyload/plugin/account/DepositfilesCom.py
new file mode 100644
index 000000000..4e09ee2ed
--- /dev/null
+++ b/pyload/plugin/account/DepositfilesCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class DepositfilesCom(Account):
+ __name = "DepositfilesCom"
+ __type = "account"
+ __version = "0.32"
+
+ __description = """Depositfiles.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("https://dfiles.eu/de/gold/")
+ validuntil = re.search(r"Sie haben Gold Zugang bis: <b>(.*?)</b></div>", html).group(1)
+
+ validuntil = time.mktime(time.strptime(validuntil, "%Y-%m-%d %H:%M:%S"))
+
+ return {"validuntil": validuntil, "trafficleft": -1}
+
+
+ def login(self, user, data, req):
+ html = req.load("https://dfiles.eu/de/login.php", get={"return": "/de/gold/payment.php"},
+ post={"login": user, "password": data['password']},
+ decode=True)
+
+ if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/DropboxCom.py b/pyload/plugin/account/DropboxCom.py
new file mode 100644
index 000000000..b8ce0e21a
--- /dev/null
+++ b/pyload/plugin/account/DropboxCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DropboxCom(SimpleHoster):
+ __name = "DropboxCom"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?dropbox\.com/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Dropbox.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<title>Dropbox - (?P<N>.+?)<'
+ SIZE_PATTERN = r'&nbsp;&middot;&nbsp; (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'<title>Dropbox - (404|Shared link error)<'
+
+ COOKIES = [("dropbox.com", "lang", "en")]
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = True
+
+
+ def handle_free(self, pyfile):
+ self.download(pyfile.url, get={'dl': "1"})
diff --git a/pyload/plugin/account/EasybytezCom.py b/pyload/plugin/account/EasybytezCom.py
new file mode 100644
index 000000000..6419369e8
--- /dev/null
+++ b/pyload/plugin/account/EasybytezCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class EasybytezCom(XFSAccount):
+ __name = "EasybytezCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """EasyBytez.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "easybytez.com"
diff --git a/pyload/plugin/account/EuroshareEu.py b/pyload/plugin/account/EuroshareEu.py
new file mode 100644
index 000000000..e1e037bf9
--- /dev/null
+++ b/pyload/plugin/account/EuroshareEu.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class EuroshareEu(Account):
+ __name = "EuroshareEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Euroshare.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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 = time.mktime(time.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/plugin/account/ExashareCom.py b/pyload/plugin/account/ExashareCom.py
new file mode 100644
index 000000000..e61c2f12d
--- /dev/null
+++ b/pyload/plugin/account/ExashareCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class ExashareCom(XFSAccount):
+ __name = "ExashareCom"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Exashare.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "exashare.com"
diff --git a/pyload/plugin/account/FastixRu.py b/pyload/plugin/account/FastixRu.py
new file mode 100644
index 000000000..ead4e63aa
--- /dev/null
+++ b/pyload/plugin/account/FastixRu.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FastixRu(Account):
+ __name = "FastixRu"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Fastix account plugin"""
+ __license = "GPLv3"
+ __authors = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ html = json_loads(req.load("http://fastix.ru/api_v2/", get={'apikey': data['api'], 'sub': "getaccountdetails"}))
+
+ points = html['points']
+ trafficleft = float(points) * 1024 ** 2 / 1000
+
+ if points > 0:
+ account_info = {"validuntil": -1, "trafficleft": trafficleft}
+ else:
+ account_info = {"validuntil": None, "trafficleft": None, "premium": False}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ html = req.load("http://fastix.ru/api_v2/",
+ get={'sub': "get_apikey", 'email': user, 'password': data['password']})
+
+ api = json_loads(html)
+ api = api['apikey']
+
+ data['api'] = api
+
+ if "error_code" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FastshareCz.py b/pyload/plugin/account/FastshareCz.py
new file mode 100644
index 000000000..f1ed9d634
--- /dev/null
+++ b/pyload/plugin/account/FastshareCz.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+from pyload.utils import parseFileSize
+
+
+class FastshareCz(Account):
+ __name = "FastshareCz"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """Fastshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ CREDIT_PATTERN = r'Credit\s*:\s*</td>\s*<td>(.+?)\s*<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = -1
+ trafficleft = None
+ premium = False
+
+ html = req.load("http://www.fastshare.cz/user", decode=True)
+
+ m = re.search(self.CREDIT_PATTERN, html)
+ if m:
+ trafficleft = self.parseTraffic(m.group(1))
+
+ premium = bool(trafficleft)
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("fastshare.cz", "lang", "en")
+
+ 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={'login': user, 'heslo': data['password']},
+ decode=True)
+
+ if ">Wrong username or password" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/File4SafeCom.py b/pyload/plugin/account/File4SafeCom.py
new file mode 100644
index 000000000..1c7e00fcf
--- /dev/null
+++ b/pyload/plugin/account/File4SafeCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class File4SafeCom(XFSAccount):
+ __name = "File4SafeCom"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """File4Safe.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ HOSTER_DOMAIN = "file4safe.com"
+
+ LOGIN_FAIL_PATTERN = r'input_login'
diff --git a/pyload/plugin/account/FileParadoxIn.py b/pyload/plugin/account/FileParadoxIn.py
new file mode 100644
index 000000000..0e103c4e7
--- /dev/null
+++ b/pyload/plugin/account/FileParadoxIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class FileParadoxIn(XFSAccount):
+ __name = "FileParadoxIn"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """FileParadox.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "fileparadox.in"
diff --git a/pyload/plugin/account/FilecloudIo.py b/pyload/plugin/account/FilecloudIo.py
new file mode 100644
index 000000000..b07fe981a
--- /dev/null
+++ b/pyload/plugin/account/FilecloudIo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FilecloudIo(Account):
+ __name = "FilecloudIo"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """FilecloudIo account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "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 _i in xrange(5):
+ rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api",
+ post={"username": user, "password": self.getAccountData(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": float(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)
+
+ if "you have successfully logged in" not in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FilefactoryCom.py b/pyload/plugin/account/FilefactoryCom.py
new file mode 100644
index 000000000..5c80c6e1c
--- /dev/null
+++ b/pyload/plugin/account/FilefactoryCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FilefactoryCom(Account):
+ __name = "FilefactoryCom"
+ __type = "account"
+ __version = "0.15"
+
+ __description = """Filefactory.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "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 = time.mktime(time.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(pycurl.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/plugin/account/FilejungleCom.py b/pyload/plugin/account/FilejungleCom.py
new file mode 100644
index 000000000..19d343372
--- /dev/null
+++ b/pyload/plugin/account/FilejungleCom.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FilejungleCom(Account):
+ __name = "FilejungleCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """Filejungle.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = 60
+
+ URL = "http://filejungle.com/"
+ TRAFFIC_LEFT_PATTERN = r'"/extend_premium\.php">Until (\d+ \w+ \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 = time.mktime(time.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": ""},
+ decode=True)
+
+ if re.search(self.LOGIN_FAILED_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FileomCom.py b/pyload/plugin/account/FileomCom.py
new file mode 100644
index 000000000..2868e49e6
--- /dev/null
+++ b/pyload/plugin/account/FileomCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class FileomCom(XFSAccount):
+ __name = "FileomCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Fileom.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "fileom.com"
diff --git a/pyload/plugin/account/FilerNet.py b/pyload/plugin/account/FilerNet.py
new file mode 100644
index 000000000..b6baddacc
--- /dev/null
+++ b/pyload/plugin/account/FilerNet.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FilerNet(Account):
+ __name = "FilerNet"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """Filer.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "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 = time.mktime(time.strptime(until.group(1), "%d.%m.%Y %H:%M:%S"))
+ trafficleft = self.parseTraffic(traffic.group(1))
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+
+ else:
+ self.logError(_("Unable to retrieve account information"))
+ 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/"},
+ decode=True)
+
+ if 'Logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FilerioCom.py b/pyload/plugin/account/FilerioCom.py
new file mode 100644
index 000000000..d222fa78b
--- /dev/null
+++ b/pyload/plugin/account/FilerioCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class FilerioCom(XFSAccount):
+ __name = "FilerioCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """FileRio.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "filerio.in"
diff --git a/pyload/plugin/account/FilesMailRu.py b/pyload/plugin/account/FilesMailRu.py
new file mode 100644
index 000000000..03b56be44
--- /dev/null
+++ b/pyload/plugin/account/FilesMailRu.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class FilesMailRu(Account):
+ __name = "FilesMailRu"
+ __type = "account"
+ __version = "0.11"
+
+ __description = """Filesmail.ru account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ return {"validuntil": None, "trafficleft": None}
+
+
+ def login(self, user, data, req):
+ user, domain = user.split("@")
+
+ html = req.load("http://swa.mail.ru/cgi-bin/auth",
+ post={"Domain": domain,
+ "Login": user,
+ "Password": data['password'],
+ "Page": "http://files.mail.ru/"},
+ decode=True)
+
+ if "НеверМПе ОЌя пПльзПвателя ОлО парПль" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FileserveCom.py b/pyload/plugin/account/FileserveCom.py
new file mode 100644
index 000000000..d68285a33
--- /dev/null
+++ b/pyload/plugin/account/FileserveCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FileserveCom(Account):
+ __name = "FileserveCom"
+ __type = "account"
+ __version = "0.20"
+
+ __description = """Fileserve.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+
+ html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(html)
+
+ if res['type'] == "premium":
+ validuntil = time.mktime(time.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):
+ html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(html)
+
+ if not res['type']:
+ self.wrongPassword()
+
+ # login at fileserv html
+ req.load("http://www.fileserve.com/login.php",
+ post={"loginUserName": user, "loginUserPassword": data['password'], "autoLogin": "checked",
+ "loginFormSubmit": "Login"})
diff --git a/pyload/plugin/account/FourSharedCom.py b/pyload/plugin/account/FourSharedCom.py
new file mode 100644
index 000000000..a04c9bc46
--- /dev/null
+++ b/pyload/plugin/account/FourSharedCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FourSharedCom(Account):
+ __name = "FourSharedCom"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """FourShared.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "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")
+
+ res = 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"},
+ decode=True)
+
+ if 'Please log in to access your 4shared account' in res:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FreakshareCom.py b/pyload/plugin/account/FreakshareCom.py
new file mode 100644
index 000000000..5bfcf9bcd
--- /dev/null
+++ b/pyload/plugin/account/FreakshareCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FreakshareCom(Account):
+ __name = "FreakshareCom"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Freakshare.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = None
+ trafficleft = None
+
+ html = req.load("http://freakshare.com/")
+
+ try:
+ m = re.search(r'ltig bis:</td>\s*<td><b>([\d.:-]+)</b></td>', html, re.M)
+ validuntil = time.mktime(time.strptime(m.group(1).strip(), "%d.%m.%Y - %H:%M"))
+
+ except Exception:
+ pass
+
+ try:
+ m = re.search(r'Traffic verbleibend:</td>\s*<td>([^<]+)', html, re.M)
+ trafficleft = self.parseTraffic(m.group(1))
+
+ except Exception:
+ pass
+
+ return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
+
+
+ def login(self, user, data, req):
+ req.load("http://freakshare.com/index.php?language=EN")
+
+ html = req.load("http://freakshare.com/login.html",
+ post={"submit": "Login", "user": user, "pass": data['password']},
+ decode=True)
+
+ if ">Wrong Username or Password" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/FreeWayMe.py b/pyload/plugin/account/FreeWayMe.py
new file mode 100644
index 000000000..e139b25a6
--- /dev/null
+++ b/pyload/plugin/account/FreeWayMe.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class FreeWayMe(Account):
+ __name = "FreeWayMe"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """FreeWayMe account plugin"""
+ __license = "GPLv3"
+ __authors = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def loadAccountInfo(self, user, req):
+ status = self.getAccountStatus(user, req)
+
+ self.logDebug(status)
+
+ account_info = {"validuntil": -1, "premium": False}
+ if status['premium'] == "Free":
+ account_info['trafficleft'] = self.parseTraffic(status['guthaben'] + "MB")
+ elif status['premium'] == "Spender":
+ account_info['trafficleft'] = -1
+ elif status['premium'] == "Flatrate":
+ account_info = {"validuntil": float(status['Flatrate']),
+ "trafficleft": -1,
+ "premium": True}
+
+ return account_info
+
+
+ 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.getAccountData(user)['password']})
+
+ self.logDebug("Login: %s" % answer)
+
+ if answer == "Invalid login":
+ self.wrongPassword()
+
+ return json_loads(answer)
diff --git a/pyload/plugin/account/FshareVn.py b/pyload/plugin/account/FshareVn.py
new file mode 100644
index 000000000..9f0beec84
--- /dev/null
+++ b/pyload/plugin/account/FshareVn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class FshareVn(Account):
+ __name = "FshareVn"
+ __type = "account"
+ __version = "0.09"
+
+ __description = """Fshare.vn account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "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.*?>([\d.]+) ([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 = time.mktime(time.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):
+ html = req.load("https://www.fshare.vn/login.php",
+ post={'LoginForm[email]' : user,
+ 'LoginForm[password]' : data['password'],
+ 'LoginForm[rememberMe]': 1,
+ 'yt0' : "Login"},
+ 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 self.parseTraffic(m.group(1) + m.group(2)) if m else 0
diff --git a/pyload/plugin/account/Ftp.py b/pyload/plugin/account/Ftp.py
new file mode 100644
index 000000000..c7983b0c2
--- /dev/null
+++ b/pyload/plugin/account/Ftp.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class Ftp(Account):
+ __name = "Ftp"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Ftp dummy account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = -1 #: Unlimited
+ info_threshold = -1 #: Unlimited
diff --git a/pyload/plugin/account/HellshareCz.py b/pyload/plugin/account/HellshareCz.py
new file mode 100644
index 000000000..68843ee80
--- /dev/null
+++ b/pyload/plugin/account/HellshareCz.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class HellshareCz(Account):
+ __name = "HellshareCz"
+ __type = "account"
+ __version = "0.16"
+
+ __description = """Hellshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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 = self.parseTraffic(credit + "MB")
+ 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/', decode=True)
+ 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, decode=True)
+
+ 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"},
+ decode=True)
+
+ if "<p>You input a wrong user name or wrong password</p>" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/Http.py b/pyload/plugin/account/Http.py
new file mode 100644
index 000000000..aacdbf89f
--- /dev/null
+++ b/pyload/plugin/account/Http.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class Http(Account):
+ __name = "Http"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Http dummy account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = -1 #: Unlimited
+ info_threshold = -1 #: Unlimited
diff --git a/pyload/plugin/account/HugefilesNet.py b/pyload/plugin/account/HugefilesNet.py
new file mode 100644
index 000000000..b4cd6f8c4
--- /dev/null
+++ b/pyload/plugin/account/HugefilesNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class HugefilesNet(XFSAccount):
+ __name = "HugefilesNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Hugefiles.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "hugefiles.net"
diff --git a/pyload/plugin/account/HundredEightyUploadCom.py b/pyload/plugin/account/HundredEightyUploadCom.py
new file mode 100644
index 000000000..15ee1a12a
--- /dev/null
+++ b/pyload/plugin/account/HundredEightyUploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class HundredEightyUploadCom(XFSAccount):
+ __name = "HundredEightyUploadCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """180upload.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "180upload.com"
diff --git a/pyload/plugin/account/JunkyvideoCom.py b/pyload/plugin/account/JunkyvideoCom.py
new file mode 100644
index 000000000..6c600f971
--- /dev/null
+++ b/pyload/plugin/account/JunkyvideoCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class JunkyvideoCom(XFSAccount):
+ __name = "JunkyvideoCom"
+ __type = "account"
+ __version = "0.01"
+
+ __description = """Junkyvideo.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "junkyvideo.com"
diff --git a/pyload/plugin/account/JunocloudMe.py b/pyload/plugin/account/JunocloudMe.py
new file mode 100644
index 000000000..75307c6dd
--- /dev/null
+++ b/pyload/plugin/account/JunocloudMe.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class JunocloudMe(XFSAccount):
+ __name = "JunocloudMe"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Junocloud.me account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "junocloud.me"
diff --git a/pyload/plugin/account/Keep2ShareCc.py b/pyload/plugin/account/Keep2ShareCc.py
new file mode 100644
index 000000000..56ac5e9ab
--- /dev/null
+++ b/pyload/plugin/account/Keep2ShareCc.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class Keep2ShareCc(Account):
+ __name = "Keep2ShareCc"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Keep2Share.cc account plugin"""
+ __license = "GPLv3"
+ __authors = [("aeronaut", "aeronaut@pianoguy.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'Premium expires:\s*<b>(.+?)<'
+ TRAFFIC_LEFT_PATTERN = r'Available traffic \(today\):\s*<b><a href="/user/statistic.html">(.+?)<'
+
+ LOGIN_FAIL_PATTERN = r'Please fix the following input errors'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = False
+
+ html = req.load("http://keep2share.cc/site/profile.html", decode=True)
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ if expiredate == "LifeTime":
+ premium = True
+ validuntil = -1
+ else:
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%Y.%m.%d"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ premium = validuntil > time.mktime(time.gmtime())
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ try:
+ trafficleft = self.parseTraffic(m.group(1))
+
+ except Exception, e:
+ self.logError(e)
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("keep2share.cc", "lang", "en")
+
+ html = req.load("http://keep2share.cc/login.html",
+ post={'LoginForm[username]' : user,
+ 'LoginForm[password]' : data['password'],
+ 'LoginForm[rememberMe]': 1,
+ 'yt0' : ""},
+ decode=True)
+
+ if re.search(self.LOGIN_FAIL_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/LetitbitNet.py b/pyload/plugin/account/LetitbitNet.py
new file mode 100644
index 000000000..0ad36f219
--- /dev/null
+++ b/pyload/plugin/account/LetitbitNet.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+# from pyload.utils import json_loads, json_dumps
+
+
+class LetitbitNet(Account):
+ __name = "LetitbitNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Letitbit.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ ## DISABLED BECAUSE IT GET 'key exausted' EVEN IF VALID ##
+ # api_key = self.getAccountData(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/plugin/account/LinestorageCom.py b/pyload/plugin/account/LinestorageCom.py
new file mode 100644
index 000000000..824d3bf77
--- /dev/null
+++ b/pyload/plugin/account/LinestorageCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class LinestorageCom(XFSAccount):
+ __name = "LinestorageCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Linestorage.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "linestorage.com"
+ HOSTER_URL = "http://linestorage.com/"
diff --git a/pyload/plugin/account/LinksnappyCom.py b/pyload/plugin/account/LinksnappyCom.py
new file mode 100644
index 000000000..942cefbee
--- /dev/null
+++ b/pyload/plugin/account/LinksnappyCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import hashlib
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class LinksnappyCom(Account):
+ __name = "LinksnappyCom"
+ __type = "account"
+ __version = "0.05"
+ __description = """Linksnappy.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "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': hashlib.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 = self.parseTraffic("%d MB" % j['return']['trafficleft'])
+
+ 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': hashlib.md5(data['password']).hexdigest()},
+ decode=True)
+
+ if 'Invalid Account Details' in r:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/MegaDebridEu.py b/pyload/plugin/account/MegaDebridEu.py
new file mode 100644
index 000000000..67af94541
--- /dev/null
+++ b/pyload/plugin/account/MegaDebridEu.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class MegaDebridEu(Account):
+ __name = "MegaDebridEu"
+ __type = "account"
+ __version = "0.20"
+
+ __description = """mega-debrid.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("D.Ducatel", "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']})
+ res = json_loads(jsonResponse)
+
+ if res['response_code'] == "ok":
+ return {"premium": True, "validuntil": float(res['vip_end']), "status": True}
+ else:
+ self.logError(res)
+ 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']})
+ res = json_loads(jsonResponse)
+ if res['response_code'] != "ok":
+ self.wrongPassword()
diff --git a/pyload/plugin/account/MegaRapidCz.py b/pyload/plugin/account/MegaRapidCz.py
new file mode 100644
index 000000000..ea788d446
--- /dev/null
+++ b/pyload/plugin/account/MegaRapidCz.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class MegaRapidCz(Account):
+ __name = "MegaRapidCz"
+ __type = "account"
+ __version = "0.35"
+
+ __description = """MegaRapid.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("MikyWoW", "mikywow@seznam.cz"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ login_timeout = 60
+
+ LIMITDL_PATTERN = ur'<td>Max. počet paralelních stahování: </td><td>(\d+)'
+ VALID_UNTIL_PATTERN = ur'<td>Paušální stahování aktivní. Vyprší </td><td><strong>(.*?)</strong>'
+ TRAFFIC_LEFT_PATTERN = r'<tr><td>Kredit</td><td>(.*?) GiB'
+
+
+ def loadAccountInfo(self, user, req):
+ htmll = req.load("http://megarapid.cz/mujucet/", decode=True)
+
+ m = re.search(self.LIMITDL_PATTERN, htmll)
+ if m:
+ data = self.getAccountData(user)
+ data['options']['limitDL'] = [int(m.group(1))]
+
+ m = re.search(self.VALID_UNTIL_PATTERN, htmll)
+ if m:
+ validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y - %H:%M"))
+ return {"premium": True, "trafficleft": -1, "validuntil": validuntil}
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, htmll)
+ 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):
+ html = req.load("http://megarapid.cz/prihlaseni/", decode=True)
+
+ if "Heslo:" in html:
+ start = html.index('id="inp_hash" name="hash" value="')
+ html = html[start + 33:]
+ hashes = html[0:32]
+ html = req.load("http://megarapid.cz/prihlaseni/",
+ post={"hash": hashes,
+ "login": user,
+ "pass1": data['password'],
+ "remember": 0,
+ "sbmt": u"Přihlásit"})
diff --git a/pyload/plugin/account/MegaRapidoNet.py b/pyload/plugin/account/MegaRapidoNet.py
new file mode 100644
index 000000000..c4ee559da
--- /dev/null
+++ b/pyload/plugin/account/MegaRapidoNet.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class MegaRapidoNet(Account):
+ __name = "MegaRapidoNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """MegaRapido.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ VALID_UNTIL_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?[\'"]premium_index[\'"].*?>[^<]*?<[^>]*?b.*?>\s*?TEMPO\s*?PREMIUM.*?<[^>]*?/b.*?>\s*?(\d*)[^\d]*?DIAS[^\d]*?(\d*)[^\d]*?HORAS[^\d]*?(\d*)[^\d]*?MINUTOS[^\d]*?(\d*)[^\d]*?SEGUNDOS'
+ USER_ID_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']checkbox_compartilhar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']usar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']user["\'][^>]*?value\s*?=\s*?["\'](.*?)\s*?["\']'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = False
+
+ html = req.load("http://megarapido.net/gerador", decode=True)
+
+ validuntil = re.search(self.VALID_UNTIL_PATTERN, html)
+ if validuntil:
+ # hier weitermachen!!! (mÌssen umbedingt die zeit richtig machen damit! (sollte aber möglich))
+ validuntil = time.time() + int(validuntil.group(1)) * 24 * 3600 + int(validuntil.group(2)) * 3600 + int(validuntil.group(3)) * 60 + int(validuntil.group(4))
+ trafficleft = -1
+ premium = True
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ req.load("http://megarapido.net/login")
+ req.load("http://megarapido.net/painel_user/ajax/logar.php",
+ post={'login': user, 'senha': data['password']},
+ decode=True)
+
+ html = req.load("http://megarapido.net/gerador")
+
+ if "sair" not in html.lower():
+ self.wrongPassword()
+ else:
+ m = re.search(self.USER_ID_PATTERN, html)
+ if m:
+ data['uid'] = m.group(1)
+ else:
+ self.fail("Couldn't find the user ID")
diff --git a/pyload/plugin/account/MegasharesCom.py b/pyload/plugin/account/MegasharesCom.py
new file mode 100644
index 000000000..8920bb2db
--- /dev/null
+++ b/pyload/plugin/account/MegasharesCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class MegasharesCom(Account):
+ __name = "MegasharesCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Megashares.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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 = '>Premium Upgrade<' not in html
+
+ validuntil = trafficleft = -1
+ try:
+ timestr = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
+ self.logDebug(timestr)
+ validuntil = time.mktime(time.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/plugin/account/MovReelCom.py b/pyload/plugin/account/MovReelCom.py
new file mode 100644
index 000000000..9eabd0a6d
--- /dev/null
+++ b/pyload/plugin/account/MovReelCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class MovReelCom(XFSAccount):
+ __name = "MovReelCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Movreel.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+ login_timeout = 60
+ info_threshold = 30
+
+ HOSTER_DOMAIN = "movreel.com"
diff --git a/pyload/plugin/account/MultihostersCom.py b/pyload/plugin/account/MultihostersCom.py
new file mode 100644
index 000000000..bc48979bb
--- /dev/null
+++ b/pyload/plugin/account/MultihostersCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.account.ZeveraCom import ZeveraCom
+
+
+class MultihostersCom(ZeveraCom):
+ __name = "MultihostersCom"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Multihosters.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("tjeh", "tjeh@gmx.net")]
+
+
+ HOSTER_DOMAIN = "multihosters.com"
diff --git a/pyload/plugin/account/MultishareCz.py b/pyload/plugin/account/MultishareCz.py
new file mode 100644
index 000000000..66ab3dd47
--- /dev/null
+++ b/pyload/plugin/account/MultishareCz.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class MultishareCz(Account):
+ __name = "MultishareCz"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Multishare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ TRAFFIC_LEFT_PATTERN = r'<span class="profil-zvyrazneni">Kredit:</span>\s*<strong>(?P<S>[\d.,]+)&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 = self.parseTraffic(m.group('S') + m.group('U')) if m else 0
+ self.premium = bool(trafficleft)
+
+ 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/plugin/account/MyfastfileCom.py b/pyload/plugin/account/MyfastfileCom.py
new file mode 100644
index 000000000..a58767b72
--- /dev/null
+++ b/pyload/plugin/account/MyfastfileCom.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(Account):
+ __name = "MyfastfileCom"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """Myfastfile.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ if 'days_left' in self.json_data:
+ validuntil = time.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/plugin/account/NetloadIn.py b/pyload/plugin/account/NetloadIn.py
new file mode 100644
index 000000000..db0f0ea5a
--- /dev/null
+++ b/pyload/plugin/account/NetloadIn.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class NetloadIn(Account):
+ __name = "NetloadIn"
+ __type = "account"
+ __version = "0.24"
+
+ __description = """Netload.in account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def api_response(self, id, password, req):
+ return req.load("http://api.netload.in/user_info.php",
+ get={'auth' : "BVm96BWDSoB4WkfbEhn42HgnjIe1ilMt",
+ 'user_id' : id,
+ 'user_password': password}).strip()
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = False
+
+ html = self.api_response(user, self.getAccountData(user)['password'], req)
+
+ if html == "-1":
+ premium = True
+
+ elif html == "0":
+ validuntil = -1
+
+ else:
+ try:
+ validuntil = time.mktime(time.strptime(html, "%Y-%m-%d %H:%M"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ self.logDebug("Valid until: %s" % validuntil)
+
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ else:
+ validuntil = -1
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ html = self.api_response(user, data['password'], req)
+
+ if not html or re.search(r'disallowed_agent|unknown_auth|login_failed', html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/NoPremiumPl.py b/pyload/plugin/account/NoPremiumPl.py
new file mode 100644
index 000000000..6cefed550
--- /dev/null
+++ b/pyload/plugin/account/NoPremiumPl.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+import hashlib
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class NoPremiumPl(Account):
+ __name = "NoPremiumPl"
+ __version = "0.01"
+ __type = "account"
+ __description = "NoPremium.pl account plugin"
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@nopremium.pl")]
+
+ _api_url = "http://crypt.nopremium.pl"
+
+ _api_query = {
+ "site": "nopremium",
+ "username": "",
+ "password": "",
+ "output": "json",
+ "loc": "1",
+ "info": "1"
+ }
+
+ _req = None
+ _usr = None
+ _pwd = None
+
+
+ def loadAccountInfo(self, name, req):
+ self._req = req
+ try:
+ result = json_loads(self.runAuthQuery())
+ except Exception:
+ #@TODO: return or let it be thrown?
+ return
+
+ premium = False
+ valid_untill = -1
+
+ if "expire" in result.keys() and result['expire']:
+ premium = True
+ valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result['expire'])).timetuple())
+ traffic_left = result['balance'] * 2 ** 20
+
+ return ({
+ "validuntil": valid_untill,
+ "trafficleft": traffic_left,
+ "premium": premium
+ })
+
+
+ def login(self, user, data, req):
+ self._usr = user
+ self._pwd = hashlib.sha1(hashlib.md5(data['password']).hexdigest()).hexdigest()
+ self._req = req
+
+ try:
+ response = json_loads(self.runAuthQuery())
+ except Exception:
+ self.wrongPassword()
+
+ if "errno" in response.keys():
+ self.wrongPassword()
+ data['usr'] = self._usr
+ data['pwd'] = self._pwd
+
+
+ def createAuthQuery(self):
+ query = self._api_query
+ query['username'] = self._usr
+ query['password'] = self._pwd
+
+ return query
+
+
+ def runAuthQuery(self):
+ data = self._req.load(self._api_url, post=self.createAuthQuery())
+
+ return data
diff --git a/pyload/plugin/account/NosuploadCom.py b/pyload/plugin/account/NosuploadCom.py
new file mode 100644
index 000000000..10f9007a6
--- /dev/null
+++ b/pyload/plugin/account/NosuploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class NosuploadCom(XFSAccount):
+ __name = "NosuploadCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Nosupload.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "nosupload.com"
diff --git a/pyload/plugin/account/NovafileCom.py b/pyload/plugin/account/NovafileCom.py
new file mode 100644
index 000000000..8400cc267
--- /dev/null
+++ b/pyload/plugin/account/NovafileCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class NovafileCom(XFSAccount):
+ __name = "NovafileCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Novafile.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "novafile.com"
diff --git a/pyload/plugin/account/NowVideoSx.py b/pyload/plugin/account/NowVideoSx.py
new file mode 100644
index 000000000..f072e3687
--- /dev/null
+++ b/pyload/plugin/account/NowVideoSx.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class NowVideoSx(Account):
+ __name = "NowVideoSx"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """NowVideo.at account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'>Your premium membership expires on: (.+?)<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = None
+
+ html = req.load("http://www.nowvideo.sx/premium.php")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%Y-%b-%d"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.nowvideo.sx/login.php",
+ post={'user': user, 'pass': data['password']},
+ decode=True)
+
+ if re.search(r'>Log In<', html):
+ self.wrongPassword()
diff --git a/pyload/plugin/account/OboomCom.py b/pyload/plugin/account/OboomCom.py
new file mode 100644
index 000000000..4669ca61e
--- /dev/null
+++ b/pyload/plugin/account/OboomCom.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+try:
+ from beaker.crypto.pbkdf2 import PBKDF2
+
+except ImportError:
+ from beaker.crypto.pbkdf2 import pbkdf2
+ from binascii import b2a_hex
+
+ class PBKDF2(object):
+
+ def __init__(self, passphrase, salt, iterations=1000):
+ self.passphrase = passphrase
+ self.salt = salt
+ self.iterations = iterations
+
+
+ def hexread(self, octets):
+ return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
+
+from pyload.utils import json_loads
+from pyload.plugin.Account import Account
+
+
+class OboomCom(Account):
+ __name = "OboomCom"
+ __type = "account"
+ __version = "0.24"
+
+ __description = """Oboom.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("stanley", "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/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 userData['premium'] == "null":
+ premium = False
+ else:
+ premium = True
+
+ if userData['premium_unix'] == "null":
+ validUntil = -1
+ else:
+ validUntil = float(userData['premium_unix'])
+
+ traffic = userData['traffic']
+
+ trafficLeft = traffic['current']
+ maxTraffic = traffic['max']
+
+ session = accountData['session']
+
+ return {'premium' : premium,
+ 'validuntil' : validUntil,
+ 'trafficleft': trafficLeft,
+ 'maxtraffic' : maxTraffic,
+ 'session' : session}
+
+
+ def login(self, user, data, req):
+ self.loadAccountData(user, req)
diff --git a/pyload/plugin/account/OneFichierCom.py b/pyload/plugin/account/OneFichierCom.py
new file mode 100644
index 000000000..531f5f735
--- /dev/null
+++ b/pyload/plugin/account/OneFichierCom.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class OneFichierCom(Account):
+ __name = "OneFichierCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """1fichier.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'Your Premium Status will end the (\d+/\d+/\d+)'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = None
+
+ html = req.load("https://1fichier.com/console/abo.pl")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1)
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%d/%m/%Y"))
+ except Exception, e:
+ self.logError(e)
+ else:
+ premium = True
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium or False}
+
+
+ def login(self, user, data, req):
+ req.http.c.setopt(pycurl.REFERER, "https://1fichier.com/login.pl?lg=en")
+
+ html = req.load("https://1fichier.com/login.pl?lg=en",
+ post={'mail' : user,
+ 'pass' : data['password'],
+ 'It' : "on",
+ 'purge' : "off",
+ 'valider': "Send"},
+ decode=True)
+
+ if '>Invalid email address' in html or '>Invalid password' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/OverLoadMe.py b/pyload/plugin/account/OverLoadMe.py
new file mode 100644
index 000000000..d65e4ad31
--- /dev/null
+++ b/pyload/plugin/account/OverLoadMe.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class OverLoadMe(Account):
+ __name = "OverLoadMe"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """Over-Load.me account plugin"""
+ __license = "GPLv3"
+ __authors = [("marley", "marley@over-load.me")]
+
+
+ def loadAccountInfo(self, user, req):
+ https = "https" if self.getConfig('ssl') else "http"
+ data = self.getAccountData(user)
+ html = req.load(https + "://api.over-load.me/account.php",
+ get={'user': user,
+ 'auth': data['password']}).strip()
+
+ data = json_loads(html)
+ self.logDebug(data)
+
+ # Check for premium
+ if data['membership'] == "Free":
+ return {'premium': False, 'validuntil': None, 'trafficleft': None}
+ else:
+ return {'premium': True, 'validuntil': data['expirationunix'], 'trafficleft': -1}
+
+
+ def login(self, user, data, req):
+ https = "https" if self.getConfig('ssl') else "http"
+ 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/plugin/account/PremiumTo.py b/pyload/plugin/account/PremiumTo.py
new file mode 100644
index 000000000..586ddf635
--- /dev/null
+++ b/pyload/plugin/account/PremiumTo.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class PremiumTo(Account):
+ __name = "PremiumTo"
+ __type = "account"
+ __version = "0.08"
+
+ __description = """Premium.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def loadAccountInfo(self, user, req):
+ traffic = req.load("http://premium.to/api/straffic.php",
+ get={'username': self.username, 'password': self.password})
+
+ if "wrong username" not in traffic:
+ trafficleft = sum(map(float, traffic.split(';')))
+ return {'premium': True, 'trafficleft': trafficleft, 'validuntil': -1}
+ else:
+ return {'premium': False, 'trafficleft': None, 'validuntil': None}
+
+
+ def login(self, user, data, req):
+ self.username = user
+ self.password = data['password']
+ authcode = req.load("http://premium.to/api/getauthcode.php",
+ get={'username': user, 'password': self.password},
+ decode=True)
+
+ if "wrong username" in authcode:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/PremiumizeMe.py b/pyload/plugin/account/PremiumizeMe.py
new file mode 100644
index 000000000..06e6ffb98
--- /dev/null
+++ b/pyload/plugin/account/PremiumizeMe.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+from pyload.utils import json_loads
+
+
+class PremiumizeMe(Account):
+ __name = "PremiumizeMe"
+ __type = "account"
+ __version = "0.13"
+
+ __description = """Premiumize.me account plugin"""
+ __license = "GPLv3"
+ __authors = [("Florian Franzen", "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'])}
+
+ 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",
+ get={'method' : "accountstatus",
+ 'params[login]': user,
+ 'params[pass]' : self.getAccountData(user)['password']})
+ return json_loads(answer)
diff --git a/pyload/plugin/account/PutdriveCom.py b/pyload/plugin/account/PutdriveCom.py
new file mode 100644
index 000000000..3d7279034
--- /dev/null
+++ b/pyload/plugin/account/PutdriveCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.account.ZeveraCom import ZeveraCom
+
+
+class PutdriveCom(ZeveraCom):
+ __name = "PutdriveCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Putdrive.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "putdrive.com"
diff --git a/pyload/plugin/account/QuickshareCz.py b/pyload/plugin/account/QuickshareCz.py
new file mode 100644
index 000000000..2f71d9ae8
--- /dev/null
+++ b/pyload/plugin/account/QuickshareCz.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class QuickshareCz(Account):
+ __name = "QuickshareCz"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Quickshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ TRAFFIC_LEFT_PATTERN = r'Stav kreditu: <strong>(.+?)</strong>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.quickshare.cz/premium", decode=True)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ trafficleft = self.parseTraffic(m.group(1))
+ premium = bool(trafficleft)
+ 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/plugin/account/RPNetBiz.py b/pyload/plugin/account/RPNetBiz.py
new file mode 100644
index 000000000..562436e85
--- /dev/null
+++ b/pyload/plugin/account/RPNetBiz.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RPNetBiz(Account):
+ __name = "RPNetBiz"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """RPNet.biz account plugin"""
+ __license = "GPLv3"
+ __authors = [("Dman", "dmanugm@gmail.com")]
+
+
+ def loadAccountInfo(self, user, req):
+ # Get account information from rpnet.biz
+ res = self.getAccountStatus(user, req)
+ try:
+ if res['accountInfo']['isPremium']:
+ # Parse account info. Change the trafficleft later to support per host info.
+ account_info = {"validuntil": float(res['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
+ res = self.getAccountStatus(user, req)
+
+ # If we have an error in the res, we have wrong login information
+ if 'error' in res:
+ self.wrongPassword()
+
+
+ def getAccountStatus(self, user, req):
+ # Using the rpnet API, check if valid premium account
+ res = req.load("https://premium.rpnet.biz/client_api.php",
+ get={"username": user, "password": self.getAccountData(user)['password'],
+ "action": "showAccountInformation"})
+ self.logDebug("JSON data: %s" % res)
+
+ return json_loads(res)
diff --git a/pyload/plugin/account/RapideoPl.py b/pyload/plugin/account/RapideoPl.py
new file mode 100644
index 000000000..c58414b53
--- /dev/null
+++ b/pyload/plugin/account/RapideoPl.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+import hashlib
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RapideoPl(Account):
+ __name = "RapideoPl"
+ __version = "0.01"
+ __type = "account"
+ __description = "Rapideo.pl account plugin"
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@rapideo.pl")]
+
+ _api_url = "http://enc.rapideo.pl"
+
+ _api_query = {
+ "site": "newrd",
+ "username": "",
+ "password": "",
+ "output": "json",
+ "loc": "1",
+ "info": "1"
+ }
+
+ _req = None
+ _usr = None
+ _pwd = None
+
+
+ def loadAccountInfo(self, name, req):
+ self._req = req
+ try:
+ result = json_loads(self.runAuthQuery())
+ except Exception:
+ #@TODO: return or let it be thrown?
+ return
+
+ premium = False
+ valid_untill = -1
+ if "expire" in result.keys() and result['expire']:
+ premium = True
+ valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result['expire'])).timetuple())
+
+ traffic_left = result['balance']
+
+ return ({
+ "validuntil": valid_untill,
+ "trafficleft": traffic_left,
+ "premium": premium
+ })
+
+
+ def login(self, user, data, req):
+ self._usr = user
+ self._pwd = hashlib.md5(data['password']).hexdigest()
+ self._req = req
+ try:
+ response = json_loads(self.runAuthQuery())
+ except Exception:
+ self.wrongPassword()
+
+ if "errno" in response.keys():
+ self.wrongPassword()
+ data['usr'] = self._usr
+ data['pwd'] = self._pwd
+
+
+ def createAuthQuery(self):
+ query = self._api_query
+ query['username'] = self._usr
+ query['password'] = self._pwd
+
+ return query
+
+
+ def runAuthQuery(self):
+ data = self._req.load(self._api_url, post=self.createAuthQuery())
+
+ return data
diff --git a/pyload/plugin/account/RapidfileshareNet.py b/pyload/plugin/account/RapidfileshareNet.py
new file mode 100644
index 000000000..0b0ed210c
--- /dev/null
+++ b/pyload/plugin/account/RapidfileshareNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class RapidfileshareNet(XFSAccount):
+ __name = "RapidfileshareNet"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Rapidfileshare.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "rapidfileshare.net"
+
+ TRAFFIC_LEFT_PATTERN = r'>Traffic available today:</TD><TD><label for="name">\s*(?P<S>[\d.,]+)\s*(?:(?P<U>[\w^_]+))?'
diff --git a/pyload/plugin/account/RapidgatorNet.py b/pyload/plugin/account/RapidgatorNet.py
new file mode 100644
index 000000000..729635037
--- /dev/null
+++ b/pyload/plugin/account/RapidgatorNet.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RapidgatorNet(Account):
+ __name = "RapidgatorNet"
+ __type = "account"
+ __version = "0.09"
+
+ __description = """Rapidgator.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://rapidgator.net/api/user"
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = False
+ sid = None
+
+ try:
+ sid = self.getAccountData(user).get('sid')
+ assert sid
+
+ html = req.load("%s/info" % self.API_URL, get={'sid': sid})
+
+ self.logDebug("API:USERINFO", html)
+
+ json = json_loads(html)
+
+ if json['response_status'] == 200:
+ if "reset_in" in json['response']:
+ self.scheduleRefresh(user, json['response']['reset_in'])
+
+ validuntil = json['response']['expire_date']
+ trafficleft = float(json['response']['traffic_left'])
+ premium = True
+ else:
+ self.logError(json['response_details'])
+
+ except Exception, e:
+ self.logError(e)
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium,
+ 'sid' : sid}
+
+
+ def login(self, user, data, req):
+ try:
+ html = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']})
+
+ self.logDebug("API:LOGIN", html)
+
+ json = json_loads(html)
+
+ 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/plugin/account/RapiduNet.py b/pyload/plugin/account/RapiduNet.py
new file mode 100644
index 000000000..c19e54a9b
--- /dev/null
+++ b/pyload/plugin/account/RapiduNet.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+from pyload.utils import json_loads
+
+
+class RapiduNet(Account):
+ __name = "RapiduNet"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Rapidu.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", None),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ PREMIUM_PATTERN = r'>Account: <b>Premium'
+
+ VALID_UNTIL_PATTERN = r'>Account: <b>\w+ \((\d+)'
+
+ TRAFFIC_LEFT_PATTERN = r'class="tipsyS"><b>(.+?)<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = -1
+ premium = False
+
+ html = req.load("https://rapidu.net/", decode=True)
+
+ if re.search(self.PREMIUM_PATTERN, html):
+ premium = True
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ validuntil = time.time() + (86400 * int(m.group(1)))
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ trafficleft = self.parseTraffic(m.group(1))
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ req.load("https://rapidu.net/ajax.php",
+ get={'a': "getChangeLang"},
+ post={'_go' : "",
+ 'lang': "en"})
+
+ json = json_loads(req.load("https://rapidu.net/ajax.php",
+ get={'a': "getUserLogin"},
+ post={'_go' : "",
+ 'login' : user,
+ 'pass' : data['password'],
+ 'remember': "1"}))
+
+ self.logDebug(json)
+
+ if not json['message'] == "success":
+ self.wrongPassword()
diff --git a/pyload/plugin/account/RarefileNet.py b/pyload/plugin/account/RarefileNet.py
new file mode 100644
index 000000000..fc736bafc
--- /dev/null
+++ b/pyload/plugin/account/RarefileNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class RarefileNet(XFSAccount):
+ __name = "RarefileNet"
+ __type = "account"
+ __version = "0.04"
+
+ __description = """RareFile.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "rarefile.net"
diff --git a/pyload/plugin/account/RealdebridCom.py b/pyload/plugin/account/RealdebridCom.py
new file mode 100644
index 000000000..0d28f2ad6
--- /dev/null
+++ b/pyload/plugin/account/RealdebridCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import xml.dom.minidom as dom
+
+from pyload.plugin.Account import Account
+
+
+class RealdebridCom(Account):
+ __name = "RealdebridCom"
+ __type = "account"
+ __version = "0.45"
+
+ __description = """Real-Debrid.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ if self.pin_code:
+ return {"premium": False}
+ html = req.load("https://real-debrid.com/api/account.php")
+ xml = dom.parseString(html)
+ account_info = {"validuntil": float(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
+ "trafficleft": -1}
+
+ return account_info
+
+
+ def login(self, user, data, req):
+ self.pin_code = False
+ html = req.load("https://real-debrid.com/ajax/login.php",
+ get={"user": user, "pass": data['password']},
+ decode=True)
+
+ if "Your login informations are incorrect" in html:
+ self.wrongPassword()
+
+ elif "PIN Code required" in html:
+ 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/plugin/account/RehostTo.py b/pyload/plugin/account/RehostTo.py
new file mode 100644
index 000000000..3b02145fc
--- /dev/null
+++ b/pyload/plugin/account/RehostTo.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class RehostTo(Account):
+ __name = "RehostTo"
+ __type = "account"
+ __version = "0.16"
+
+ __description = """Rehost.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ trafficleft = None
+ validuntil = -1
+ session = ""
+
+ html = req.load("http://rehost.to/api.php",
+ get={'cmd' : "login", 'user': user,
+ 'pass': self.getAccountData(user)['password']})
+ try:
+ session = html.split(",")[1].split("=")[1]
+
+ html = req.load("http://rehost.to/api.php",
+ get={'cmd': "get_premium_credits", 'long_ses': session})
+
+ if html.strip() == "0,0" or "ERROR" in html:
+ self.logDebug(html)
+ else:
+ traffic, valid = html.split(",")
+
+ premium = True
+ trafficleft = self.parseTraffic(traffic + "MB")
+ validuntil = float(valid)
+
+ finally:
+ return {'premium' : premium,
+ 'trafficleft': trafficleft,
+ 'validuntil' : validuntil,
+ 'session' : session}
+
+
+ def login(self, user, data, req):
+ html = req.load("http://rehost.to/api.php",
+ get={'cmd': "login", 'user': user, 'pass': data['password']},
+ decode=True)
+
+ if "ERROR" in html:
+ self.logDebug(html)
+ self.wrongPassword()
diff --git a/pyload/plugin/account/RyushareCom.py b/pyload/plugin/account/RyushareCom.py
new file mode 100644
index 000000000..f34d1d388
--- /dev/null
+++ b/pyload/plugin/account/RyushareCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class RyushareCom(XFSAccount):
+ __name = "RyushareCom"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """Ryushare.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "ryushare.com"
diff --git a/pyload/plugin/account/SafesharingEu.py b/pyload/plugin/account/SafesharingEu.py
new file mode 100644
index 000000000..eae8140fb
--- /dev/null
+++ b/pyload/plugin/account/SafesharingEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class SafesharingEu(XFSAccount):
+ __name = "SafesharingEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Safesharing.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "safesharing.eu"
diff --git a/pyload/plugin/account/SecureUploadEu.py b/pyload/plugin/account/SecureUploadEu.py
new file mode 100644
index 000000000..b349e893f
--- /dev/null
+++ b/pyload/plugin/account/SecureUploadEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class SecureUploadEu(XFSAccount):
+ __name = "SecureUploadEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """SecureUpload.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "secureupload.eu"
diff --git a/pyload/plugin/account/SendmywayCom.py b/pyload/plugin/account/SendmywayCom.py
new file mode 100644
index 000000000..add0b2183
--- /dev/null
+++ b/pyload/plugin/account/SendmywayCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class SendmywayCom(XFSAccount):
+ __name = "SendmywayCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Sendmyway.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "sendmyway.com"
diff --git a/pyload/plugin/account/ShareonlineBiz.py b/pyload/plugin/account/ShareonlineBiz.py
new file mode 100644
index 000000000..81f96c014
--- /dev/null
+++ b/pyload/plugin/account/ShareonlineBiz.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class ShareonlineBiz(Account):
+ __name = "ShareonlineBiz"
+ __type = "account"
+ __version = "0.33"
+
+ __description = """Share-online.biz account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def api_response(self, user, req):
+ return req.load("http://api.share-online.biz/cgi-bin",
+ get={'q' : "userdetails",
+ 'aux' : "traffic",
+ 'username': user,
+ 'password': self.getAccountData(user)['password']})
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = None
+ trafficleft = -1
+ maxtraffic = 100 * 2 ** 30 #: 100 GB
+
+ api = {}
+ for line in self.api_response(user, req).splitlines():
+ if "=" in line:
+ key, value = line.split("=")
+ api[key] = value
+
+ self.logDebug(api)
+
+ if api['a'].lower() != "not_available":
+ req.cj.setCookie("share-online.biz", 'a', api['a'])
+
+ premium = api['group'] in ("PrePaid", "Premium", "Penalty-Premium")
+
+ validuntil = float(api['expire_date'])
+
+ traffic = float(api['traffic_1d'].split(";")[0])
+
+ if maxtraffic > traffic:
+ trafficleft = maxtraffic - traffic
+ else:
+ trafficleft = -1
+
+ return {'premium' : premium,
+ 'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'maxtraffic' : maxtraffic}
+
+
+ def login(self, user, data, req):
+ html = self.api_response(user, req)
+ err = re.search(r'\*\*(.+?)\*\*', html)
+ if err:
+ self.logError(err.group(1))
+ self.wrongPassword()
diff --git a/pyload/plugin/account/SimplyPremiumCom.py b/pyload/plugin/account/SimplyPremiumCom.py
new file mode 100644
index 000000000..2547dad56
--- /dev/null
+++ b/pyload/plugin/account/SimplyPremiumCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.Account import Account
+
+
+class SimplyPremiumCom(Account):
+ __name = "SimplyPremiumCom"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Simply-Premium.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium = False
+ validuntil = -1
+ trafficleft = None
+
+ json_data = req.load('http://www.simply-premium.com/api/user.php?format=json')
+
+ self.logDebug("JSON data: %s" % json_data)
+
+ json_data = json_loads(json_data)
+
+ if 'vip' in json_data['result'] and json_data['result']['vip']:
+ premium = True
+
+ if 'timeend' in json_data['result'] and json_data['result']['timeend']:
+ validuntil = float(json_data['result']['timeend'])
+
+ if 'remain_traffic' in json_data['result'] and json_data['result']['remain_traffic']:
+ trafficleft = float(json_data['result']['remain_traffic'])
+
+ return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft}
+
+
+ def login(self, user, data, req):
+ req.cj.setCookie("simply-premium.com", "lang", "EN")
+
+ html = req.load("http://www.simply-premium.com/login.php",
+ post={'key': user} if not data['password'] else {'login_name': user, 'login_pass': data['password']},
+ decode=True)
+
+ if 'logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/SimplydebridCom.py b/pyload/plugin/account/SimplydebridCom.py
new file mode 100644
index 000000000..dc855c441
--- /dev/null
+++ b/pyload/plugin/account/SimplydebridCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+
+
+class SimplydebridCom(Account):
+ __name = "SimplydebridCom"
+ __type = "account"
+ __version = "0.11"
+
+ __description = """Simply-Debrid.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def loadAccountInfo(self, user, req):
+ get_data = {'login': 2, 'u': self.loginname, 'p': self.password}
+ res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
+ data = [x.strip() for x in res.split(";")]
+ if str(data[0]) != "1":
+ return {"premium": False}
+ else:
+ return {"trafficleft": -1, "validuntil": time.mktime(time.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}
+
+ res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
+ if res != "02: loggin success":
+ self.wrongPassword()
diff --git a/pyload/plugin/account/SmoozedCom.py b/pyload/plugin/account/SmoozedCom.py
new file mode 100644
index 000000000..63381c20b
--- /dev/null
+++ b/pyload/plugin/account/SmoozedCom.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import hashlib
+import time
+
+try:
+ from beaker.crypto.pbkdf2 import PBKDF2
+
+except ImportError:
+ from beaker.crypto.pbkdf2 import pbkdf2
+ from binascii import b2a_hex
+
+ class PBKDF2(object):
+
+ def __init__(self, passphrase, salt, iterations=1000):
+ self.passphrase = passphrase
+ self.salt = salt
+ self.iterations = iterations
+
+
+ def hexread(self, octets):
+ return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets))
+
+from pyload.utils import json_loads
+from pyload.plugin.Account import Account
+
+
+class SmoozedCom(Account):
+ __name = "SmoozedCom"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """Smoozed.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("", "")]
+
+
+ def loadAccountInfo(self, user, req):
+ status = self.getAccountStatus(user, req)
+
+ self.logDebug(status)
+
+ if status['state'] != 'ok':
+ info = {'validuntil' : None,
+ 'trafficleft': None,
+ 'premium' : False}
+ else:
+ # Parse account info
+ info = {'validuntil' : float(status['data']['user']['user_premium']),
+ 'trafficleft': max(0, status['data']['traffic'][1] - status['data']['traffic'][0]),
+ 'session' : status['data']['session_key'],
+ 'hosters' : [hoster['name'] for hoster in status['data']['hoster']]}
+
+ if info['validuntil'] < time.time():
+ if float(status["data"]["user"].get("user_trial", 0)) > time.time():
+ info['premium'] = True
+ else:
+ info['premium'] = False
+ else:
+ info['premium'] = True
+
+ return 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['state'] != 'ok':
+ self.wrongPassword()
+
+
+ def getAccountStatus(self, user, req):
+ password = self.getAccountData(user)['password']
+ salt = hashlib.sha256(password).hexdigest()
+ encrypted = PBKDF2(password, salt, iterations=1000).hexread(32)
+
+ return json_loads(req.load("http://www2.smoozed.com/api/login",
+ get={'auth': user, 'password': encrypted}))
diff --git a/pyload/plugin/account/StahnuTo.py b/pyload/plugin/account/StahnuTo.py
new file mode 100644
index 000000000..57f9adc10
--- /dev/null
+++ b/pyload/plugin/account/StahnuTo.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class StahnuTo(Account):
+ __name = "StahnuTo"
+ __type = "account"
+ __version = "0.05"
+
+ __description = """StahnuTo account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.stahnu.to/")
+
+ m = re.search(r'>VIP: (\d+.*)<', html)
+ trafficleft = self.parseTraffic(m.group(1)) if m else 0
+
+ return {"premium": trafficleft > 512, "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"},
+ decode=True)
+
+ if not '<a href="logout.php">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/StreamcloudEu.py b/pyload/plugin/account/StreamcloudEu.py
new file mode 100644
index 000000000..f3eb6cce9
--- /dev/null
+++ b/pyload/plugin/account/StreamcloudEu.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class StreamcloudEu(XFSAccount):
+ __name = "StreamcloudEu"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Streamcloud.eu account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "streamcloud.eu"
diff --git a/pyload/plugin/account/TurbobitNet.py b/pyload/plugin/account/TurbobitNet.py
new file mode 100644
index 000000000..4b1b6b2a0
--- /dev/null
+++ b/pyload/plugin/account/TurbobitNet.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class TurbobitNet(Account):
+ __name = "TurbobitNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """TurbobitNet account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://turbobit.net")
+
+ m = re.search(r'<u>Turbo Access</u> to ([\d.]+)', html)
+ if m:
+ premium = True
+ validuntil = time.mktime(time.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"},
+ decode=True)
+
+ if not '<div class="menu-item user-name">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/TusfilesNet.py b/pyload/plugin/account/TusfilesNet.py
new file mode 100644
index 000000000..df22a36e6
--- /dev/null
+++ b/pyload/plugin/account/TusfilesNet.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class TusfilesNet(XFSAccount):
+ __name = "TusfilesNet"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """Tusfile.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ HOSTER_DOMAIN = "tusfiles.net"
+
+ VALID_UNTIL_PATTERN = r'<span class="label label-default">([^<]+)</span>'
+ TRAFFIC_LEFT_PATTERN = r'<td><img src="//www\.tusfiles\.net/i/icon/meter\.png" alt=""/></td>\n<td>&nbsp;(?P<S>[\d.,]+)'
diff --git a/pyload/plugin/account/UlozTo.py b/pyload/plugin/account/UlozTo.py
new file mode 100644
index 000000000..20f03f907
--- /dev/null
+++ b/pyload/plugin/account/UlozTo.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.Account import Account
+
+
+class UlozTo(Account):
+ __name = "UlozTo"
+ __type = "account"
+ __version = "0.10"
+
+ __description = """Uloz.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("pulpe", "")]
+
+
+ TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a .*?title=".+?GB = ([\d.]+) MB"'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.ulozto.net/", decode=True)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+
+ trafficleft = float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048 if m else 0
+ premium = bool(trafficleft)
+
+ return {'validuntil': -1, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ 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(urlparse.urljoin("http://www.ulozto.net/", action),
+ post={'_token_' : token,
+ 'do' : "loginForm-submit",
+ 'login' : u"Přihlásit",
+ 'password': data['password'],
+ 'username': user,
+ 'remember': "on"},
+ decode=True)
+
+ if '<div class="flash error">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadableCh.py b/pyload/plugin/account/UploadableCh.py
new file mode 100644
index 000000000..c95fe7f0b
--- /dev/null
+++ b/pyload/plugin/account/UploadableCh.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Account import Account
+
+
+class UploadableCh(Account):
+ __name = "UploadableCh"
+ __type = "account"
+ __version = "0.03"
+
+ __description = """Uploadable.ch account plugin"""
+ __license = "GPLv3"
+ __authors = [("Sasch", "gsasch@gmail.com")]
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.uploadable.ch/login.php")
+
+ premium = '<a href="/logout.php"' in html
+ trafficleft = -1 if premium else None
+
+ return {'validuntil': None, 'trafficleft': trafficleft, 'premium': premium} #@TODO: validuntil
+
+
+ def login(self, user, data, req):
+ html = req.load("http://www.uploadable.ch/login.php",
+ post={'userName' : user,
+ 'userPassword' : data['password'],
+ 'autoLogin' : "1",
+ 'action__login': "normalLogin"},
+ decode=True)
+
+ if "Login failed" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadcCom.py b/pyload/plugin/account/UploadcCom.py
new file mode 100644
index 000000000..01102168c
--- /dev/null
+++ b/pyload/plugin/account/UploadcCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class UploadcCom(XFSAccount):
+ __name = "UploadcCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """Uploadc.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "uploadc.com"
diff --git a/pyload/plugin/account/UploadedTo.py b/pyload/plugin/account/UploadedTo.py
new file mode 100644
index 000000000..1b8ae5b27
--- /dev/null
+++ b/pyload/plugin/account/UploadedTo.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class UploadedTo(Account):
+ __name = "UploadedTo"
+ __type = "account"
+ __version = "0.30"
+
+ __description = """Uploaded.to account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ PREMIUM_PATTERN = r'<em>Premium</em>'
+ VALID_UNTIL_PATTERN = r'<td>Duration:</td>\s*<th>(.+?)<'
+ TRAFFIC_LEFT_PATTERN = r'<b class="cB">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = None
+
+ html = req.load("http://uploaded.net/me")
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is not None
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html, re.M)
+ if m:
+ expiredate = m.group(1).lower().strip()
+
+ if expiredate == "unlimited":
+ validuntil = -1
+ else:
+ m = re.findall(r'(\d+) (week|day|hour)', expiredate)
+ if m:
+ validuntil = time.time()
+ for n, u in m:
+ validuntil += float(n) * 60 * 60 * {'week': 168, 'day': 24, 'hour': 1}[u]
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ traffic = m.groupdict()
+ size = traffic['S'].replace('.', '')
+ unit = traffic['U'].lower()
+
+ trafficleft = self.parseTraffic(size + unit)
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ # req.cj.setCookie("uploaded.net", "lang", "en")
+
+ html = req.load("http://uploaded.net/io/login",
+ post={'id': user, 'pw': data['password'], '_': ""},
+ decode=True)
+
+ if '"err"' in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadheroCom.py b/pyload/plugin/account/UploadheroCom.py
new file mode 100644
index 000000000..a0c8e2aa8
--- /dev/null
+++ b/pyload/plugin/account/UploadheroCom.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+import datetime
+import time
+
+from pyload.plugin.Account import Account
+
+
+class UploadheroCom(Account):
+ __name = "UploadheroCom"
+ __type = "account"
+ __version = "0.21"
+
+ __description = """Uploadhero.co account plugin"""
+ __license = "GPLv3"
+ __authors = [("mcmyst", "mcmyst@hotmail.fr")]
+
+
+ def loadAccountInfo(self, user, req):
+ premium_pattern = re.compile('Il vous reste <span class="bleu">(\d+)</span> jours premium')
+
+ data = self.getAccountData(user)
+ html = req.load("http://uploadhero.co/my-account")
+
+ if premium_pattern.search(html):
+ end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(html).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):
+ html = req.load("http://uploadhero.co/lib/connexion.php",
+ post={"pseudo_login": user, "password_login": data['password']},
+ decode=True)
+
+ if "mot de passe invalide" in html:
+ self.wrongPassword()
diff --git a/pyload/plugin/account/UploadingCom.py b/pyload/plugin/account/UploadingCom.py
new file mode 100644
index 000000000..992e63615
--- /dev/null
+++ b/pyload/plugin/account/UploadingCom.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Account import Account
+from pyload.plugin.internal.SimpleHoster import set_cookies
+
+
+class UploadingCom(Account):
+ __name = "UploadingCom"
+ __type = "account"
+ __version = "0.12"
+
+ __description = """Uploading.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ PREMIUM_PATTERN = r'UPGRADE TO PREMIUM'
+ VALID_UNTIL_PATTERN = r'Valid Until:(.+?)<'
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = None
+
+ html = req.load("http://uploading.com/")
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is None
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%b %d, %Y"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ else:
+ premium = False
+ validuntil = None
+
+ return {'validuntil' : validuntil,
+ 'trafficleft': trafficleft,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ set_cookies(req.cj,
+ [("uploading.com", "lang" , "1" ),
+ ("uploading.com", "language", "1" ),
+ ("uploading.com", "setlang" , "en"),
+ ("uploading.com", "_lang" , "en")])
+
+ req.load("http://uploading.com/")
+ req.load("http://uploading.com/general/login_form/?JsHttpRequest=%s-xml" % long(time.time() * 1000),
+ post={'email': user, 'password': data['password'], 'remember': "on"})
diff --git a/pyload/plugin/account/UptoboxCom.py b/pyload/plugin/account/UptoboxCom.py
new file mode 100644
index 000000000..f7e715a33
--- /dev/null
+++ b/pyload/plugin/account/UptoboxCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class UptoboxCom(XFSAccount):
+ __name = "UptoboxCom"
+ __type = "account"
+ __version = "0.08"
+
+ __description = """DDLStorage.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "uptobox.com"
+ HOSTER_URL = "https://uptobox.com/"
+ LOGIN_URL = "https://login.uptobox.com/"
diff --git a/pyload/plugin/account/VidPlayNet.py b/pyload/plugin/account/VidPlayNet.py
new file mode 100644
index 000000000..c5d4e0b5a
--- /dev/null
+++ b/pyload/plugin/account/VidPlayNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class VidPlayNet(XFSAccount):
+ __name = "VidPlayNet"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """VidPlay.net account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "vidplay.net"
diff --git a/pyload/plugin/account/WebshareCz.py b/pyload/plugin/account/WebshareCz.py
new file mode 100644
index 000000000..35640dba0
--- /dev/null
+++ b/pyload/plugin/account/WebshareCz.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import hashlib
+import passlib
+import re
+import time
+
+from pyload.plugin.Account import Account
+
+
+class WebshareCz(Account):
+ __name = "WebshareCz"
+ __type = "account"
+ __version = "0.07"
+
+ __description = """Webshare.cz account plugin"""
+ __license = "GPLv3"
+ __authors = [("rush", "radek.senfeld@gmail.com")]
+
+
+ VALID_UNTIL_PATTERN = r'<vip_until>(.+)</vip_until>'
+
+ TRAFFIC_LEFT_PATTERN = r'<bytes>(.+)</bytes>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("https://webshare.cz/api/user_data/",
+ post={'wst': self.infos['wst']},
+ decode=True)
+
+ self.logDebug("Response: " + html)
+
+ expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
+ self.logDebug("Expire date: " + expiredate)
+
+ validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S"))
+ trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1))
+ premium = validuntil > time.time()
+
+ return {'validuntil': validuntil, 'trafficleft': -1, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ salt = req.load("https://webshare.cz/api/salt/",
+ post={'username_or_email': user,
+ 'wst' : ""},
+ decode=True)
+
+ if "<status>OK</status>" not in salt:
+ self.wrongPassword()
+
+ salt = re.search('<salt>(.+)</salt>', salt).group(1)
+ password = hashlib.sha1(passlib.hash.md5_crypt.encrypt(data['password'], salt=salt)).hexdigest()
+ digest = hashlib.md5(user + ":Webshare:" + password).hexdigest()
+
+ login = req.load("https://webshare.cz/api/login/",
+ post={'digest' : digest,
+ 'keep_logged_in' : 1,
+ 'password' : password,
+ 'username_or_email': user,
+ 'wst' : ""},
+ decode=True)
+
+ if "<status>OK</status>" not in login:
+ self.wrongPassword()
+
+ self.infos['wst'] = re.search('<token>(.+)</token>', login).group(1)
diff --git a/pyload/plugin/account/XFileSharingPro.py b/pyload/plugin/account/XFileSharingPro.py
new file mode 100644
index 000000000..79b5427ef
--- /dev/null
+++ b/pyload/plugin/account/XFileSharingPro.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSAccount import XFSAccount
+
+
+class XFileSharingPro(XFSAccount):
+ __name = "XFileSharingPro"
+ __type = "account"
+ __version = "0.06"
+
+ __description = """XFileSharingPro multi-purpose account plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+
+
+ def init(self):
+ if self.HOSTER_DOMAIN:
+ return super(XFileSharingPro, self).init()
+
+
+ def loadAccountInfo(self, user, req):
+ return super(XFileSharingPro if self.HOSTER_DOMAIN else XFSAccount, self).loadAccountInfo(user, req)
+
+
+ def login(self, user, data, req):
+ if self.HOSTER_DOMAIN:
+ try:
+ return super(XFileSharingPro, self).login(user, data, req)
+ except Exception:
+ self.HOSTER_URL = self.HOSTER_URL.replace("www.", "")
+ return super(XFileSharingPro, self).login(user, data, req)
diff --git a/pyload/plugin/account/YibaishiwuCom.py b/pyload/plugin/account/YibaishiwuCom.py
new file mode 100644
index 000000000..e12e3f3f2
--- /dev/null
+++ b/pyload/plugin/account/YibaishiwuCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Account import Account
+
+
+class YibaishiwuCom(Account):
+ __name = "YibaishiwuCom"
+ __type = "account"
+ __version = "0.02"
+
+ __description = """115.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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 = m and 'is_vip: 1' in m.group(1)
+ 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/plugin/account/ZeveraCom.py b/pyload/plugin/account/ZeveraCom.py
new file mode 100644
index 000000000..1e5eacb4c
--- /dev/null
+++ b/pyload/plugin/account/ZeveraCom.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Account import Account
+
+
+class ZeveraCom(Account):
+ __name = "ZeveraCom"
+ __type = "account"
+ __version = "0.26"
+
+ __description = """Zevera.com account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = "zevera.com"
+
+
+ def init(self):
+ if not self.HOSTER_DOMAIN:
+ self.logError(_("Missing HOSTER_DOMAIN"))
+
+ if not hasattr(self, "API_URL"):
+ self.API_URL = "http://api.%s/jDownloader.ashx" % (self.HOSTER_DOMAIN or "")
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ premium = False
+
+ api = self.api_response(req)
+
+ if "No trafic" not in api and api['endsubscriptiondate'] != "Expired!":
+ validuntil = time.mktime(time.strptime(api['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S"))
+ trafficleft = float(api['availabletodaytraffic']) * 2 ** 20 if api['orondaytrafficlimit'] != '0' else -1
+ premium = True
+
+ return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium}
+
+
+ def login(self, user, data, req):
+ self.user = user
+ self.password = data['password']
+
+ if self.api_response(req) == "No trafic":
+ self.wrongPassword()
+
+
+ def api_response(self, req, just_header=False, **kwargs):
+ get_data = {'cmd' : "accountinfo",
+ 'login': self.user,
+ 'pass' : self.password}
+
+ get_data.update(kwargs)
+
+ res = req.load(self.API_URL,
+ get=get_data,
+ just_header=just_header,
+ decode=True)
+
+ self.logDebug(res)
+
+ if ':' in res:
+ if not just_header:
+ res = res.replace(',', '\n')
+ return dict((y.strip().lower(), z.strip()) for (y, z) in
+ [x.split(':', 1) for x in res.splitlines() if ':' in x])
+ else:
+ return res
diff --git a/pyload/plugin/account/__init__.py b/pyload/plugin/account/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/account/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/addon/AndroidPhoneNotify.py b/pyload/plugin/addon/AndroidPhoneNotify.py
new file mode 100644
index 000000000..8332f668d
--- /dev/null
+++ b/pyload/plugin/addon/AndroidPhoneNotify.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Addon import Addon, Expose
+
+
+class AndroidPhoneNotify(Addon):
+ __name = "AndroidPhoneNotify"
+ __type = "addon"
+ __version = "0.07"
+
+ __config = [("apikey" , "str" , "API key" , "" ),
+ ("notifycaptcha" , "bool", "Notify captcha request" , True ),
+ ("notifypackage" , "bool", "Notify package finished" , True ),
+ ("notifyprocessed", "bool", "Notify packages processed" , True ),
+ ("notifyupdate" , "bool", "Notify plugin updates" , True ),
+ ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
+ ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
+ ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
+ ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
+
+ __description = """Send push notifications to your Android Phone (using notifymyandroid.com)"""
+ __license = "GPLv3"
+ __authors = [("Steven Kosyra" , "steven.kosyra@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ event_list = ["allDownloadsProcessed", "plugin_updated"]
+
+
+ def setup(self):
+ self.last_notify = 0
+ self.notifications = 0
+
+
+ def plugin_updated(self, type_plugins):
+ if not self.getConfig('notifyupdate'):
+ return
+
+ self.notify(_("Plugins updated"), str(type_plugins))
+
+
+ def exit(self):
+ if not self.getConfig('notifyexit'):
+ return
+
+ if self.core.do_restart:
+ self.notify(_("Restarting pyLoad"))
+ else:
+ self.notify(_("Exiting pyLoad"))
+
+
+ def newCaptchaTask(self, task):
+ if not self.getConfig('notifycaptcha'):
+ return
+
+ self.notify(_("Captcha"), _("New request waiting user input"))
+
+
+ def packageFinished(self, pypack):
+ if self.getConfig('notifypackage'):
+ self.notify(_("Package finished"), pypack.name)
+
+
+ def allDownloadsProcessed(self):
+ if not self.getConfig('notifyprocessed'):
+ return
+
+ if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
+ self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
+ else:
+ self.notify(_("All packages finished"))
+
+
+ @Expose
+ def notify(self,
+ event,
+ msg="",
+ key=self.getConfig('apikey')):
+
+ if not key:
+ return
+
+ if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
+ return
+
+ elapsed_time = time.time() - self.last_notify
+
+ if elapsed_time < self.getConf("sendtimewait"):
+ return
+
+ if elapsed_time > 60:
+ self.notifications = 0
+
+ elif self.notifications >= self.getConf("sendpermin"):
+ return
+
+
+ getURL("http://www.notifymyandroid.com/publicapi/notify",
+ get={'apikey' : key,
+ 'application': "pyLoad",
+ 'event' : event,
+ 'description': msg})
+
+ self.last_notify = time.time()
+ self.notifications += 1
diff --git a/pyload/plugin/addon/AntiVirus.py b/pyload/plugin/addon/AntiVirus.py
new file mode 100644
index 000000000..e88dfd0f3
--- /dev/null
+++ b/pyload/plugin/addon/AntiVirus.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+import os
+import shutil
+import subprocess
+
+try:
+ import send2trash
+except ImportError:
+ pass
+
+from pyload.plugin.Addon import Addon, Expose, threaded
+from pyload.utils import fs_encode, fs_join
+
+
+class AntiVirus(Addon):
+ __name = "AntiVirus"
+ __type = "addon"
+ __version = "0.09"
+
+ #@TODO: add trash option (use Send2Trash lib)
+ __config = [("action" , "Antivirus default;Delete;Quarantine", "Manage infected files" , "Antivirus default"),
+ ("quardir" , "folder" , "Quarantine folder" , "" ),
+ ("deltotrash", "bool" , "Move to trash (recycle bin) instead delete", True ),
+ ("scanfailed", "bool" , "Scan incompleted files (failed downloads)" , False ),
+ ("cmdfile" , "file" , "Antivirus executable" , "" ),
+ ("cmdargs" , "str" , "Scan options" , "" ),
+ ("ignore-err", "bool" , "Ignore scan errors" , False )]
+
+ __description = """Scan downloaded files with antivirus program"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+
+
+
+ @Expose
+ @threaded
+ def scan(self, pyfile, thread):
+ file = fs_encode(pyfile.plugin.lastDownload)
+ filename = os.path.basename(pyfile.plugin.lastDownload)
+ cmdfile = fs_encode(self.getConfig('cmdfile'))
+ cmdargs = fs_encode(self.getConfig('cmdargs').strip())
+
+ if not os.path.isfile(file) or not os.path.isfile(cmdfile):
+ return
+
+ thread.addActive(pyfile)
+ pyfile.setCustomStatus(_("virus scanning"))
+ pyfile.setProgress(0)
+
+ try:
+ p = subprocess.Popen([cmdfile, cmdargs, file], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ out, err = map(str.strip, p.communicate())
+
+ if out:
+ self.logInfo(filename, out)
+
+ if err:
+ self.logWarning(filename, err)
+ if not self.getConfig('ignore-err'):
+ self.logDebug("Delete/Quarantine task is aborted")
+ return
+
+ if p.returncode:
+ pyfile.error = _("infected file")
+ action = self.getConfig('action')
+ try:
+ if action == "Delete":
+ if not self.getConfig('deltotrash'):
+ os.remove(file)
+
+ else:
+ try:
+ send2trash.send2trash(file)
+
+ except NameError:
+ self.logWarning(_("Send2Trash lib not found, moving to quarantine instead"))
+ pyfile.setCustomStatus(_("file moving"))
+ shutil.move(file, self.getConfig('quardir'))
+
+ except Exception, e:
+ self.logWarning(_("Unable to move file to trash: %s, moving to quarantine instead") % e.message)
+ pyfile.setCustomStatus(_("file moving"))
+ shutil.move(file, self.getConfig('quardir'))
+
+ else:
+ self.logDebug(_("Successfully moved file to trash"))
+
+ elif action == "Quarantine":
+ pyfile.setCustomStatus(_("file moving"))
+ shutil.move(file, self.getConfig('quardir'))
+
+ except (IOError, shutil.Error), e:
+ self.logError(filename, action + " action failed!", e)
+
+ elif not out and not err:
+ self.logDebug(filename, "No infected file found")
+
+ finally:
+ pyfile.setProgress(100)
+ thread.finishFile(pyfile)
+
+
+ def downloadFinished(self, pyfile):
+ return self.scan(pyfile)
+
+
+ def downloadFailed(self, pyfile):
+ #: Check if pyfile is still "failed",
+ # maybe might has been restarted in meantime
+ if pyfile.status == 8 and self.getConfig('scanfailed'):
+ return self.scan(pyfile)
diff --git a/pyload/plugin/addon/Checksum.py b/pyload/plugin/addon/Checksum.py
new file mode 100644
index 000000000..ada52d56e
--- /dev/null
+++ b/pyload/plugin/addon/Checksum.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import hashlib
+import os
+import re
+import zlib
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_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), ''):
+ 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), ''):
+ last = hf(chunk, last)
+
+ return "%x" % last
+
+ else:
+ return None
+
+
+class Checksum(Addon):
+ __name = "Checksum"
+ __type = "addon"
+ __version = "0.16"
+
+ __config = [("activated" , "bool" , "Activated" , True ),
+ ("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"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("stickell" , "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 activate(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()
+
+ elif hasattr(pyfile.plugin, "info") and isinstance(pyfile.plugin.info, dict):
+ data = pyfile.plugin.info.copy()
+ data.pop('size', None) #@NOTE: Don't check file size until a similary matcher will be implemented
+
+ 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.get("general", "download_folder")
+ # local_file = fs_encode(fs_join(download_folder, pyfile.package().folder, pyfile.name))
+
+ if not os.path.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 = os.path.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")
+
+ data.pop('size', None)
+
+ # validate checksum
+ if data and self.getConfig('check_checksum'):
+
+ if not 'md5' in data:
+ for type in ("checksum", "hashsum", "hash"):
+ if type in data:
+ data['md5'] = data[type] #@NOTE: What happens if it's not an md5 hash?
+ break
+
+ 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"), key.upper())
+ else:
+ self.logWarning(_("Unable to validate checksum for file: ") + 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:
+ os.remove(local_file)
+ pyfile.plugin.retry(max_tries, self.getConfig('wait_time'), msg)
+ elif retry_action == "nothing":
+ return
+ elif check_action == "nothing":
+ return
+ pyfile.plugin.fail(reason=msg)
+
+
+ def packageFinished(self, pypack):
+ download_folder = fs_join(self.config.get("general", "download_folder"), pypack.folder, "")
+
+ for link in pypack.getChildren().itervalues():
+ file_type = os.path.splitext(link['name'])[1][1:].lower()
+
+ if file_type not in self.formats:
+ continue
+
+ hash_file = fs_encode(fs_join(download_folder, link['name']))
+ if not os.path.isfile(hash_file):
+ self.logWarning(_("File not found"), 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(fs_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/plugin/addon/ClickNLoad.py b/pyload/plugin/addon/ClickNLoad.py
new file mode 100644
index 000000000..93c66e302
--- /dev/null
+++ b/pyload/plugin/addon/ClickNLoad.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+import socket
+import threading
+import time
+
+try:
+ import ssl
+except ImportError:
+ pass
+
+from pyload.plugin.Addon import Addon, threaded
+
+
+def forward(source, destination):
+ try:
+ bufsize = 1024
+ bufdata = source.recv(bufsize)
+ while bufdata:
+ destination.sendall(bufdata)
+ bufdata = source.recv(bufsize)
+ finally:
+ destination.shutdown(socket.SHUT_WR)
+ # destination.close()
+
+
+#@TODO: IPv6 support
+class ClickNLoad(Addon):
+ __name = "ClickNLoad"
+ __type = "addon"
+ __version = "0.43"
+
+ __config = [("activated", "bool", "Activated" , True),
+ ("port" , "int" , "Port" , 9666),
+ ("extern" , "bool", "Listen on the public network interface", True)]
+
+ __description = """Click'n'Load addon plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.de" ),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def activate(self):
+ if not self.core.config.get("webui", "activated"):
+ return
+
+ ip = "" if self.getConfig('extern') else "127.0.0.1"
+ webport = self.core.config.get("webui", "port")
+ cnlport = self.getConfig('port')
+
+ self.proxy(ip, webport, cnlport)
+
+
+ @threaded
+ def proxy(self, ip, webport, cnlport):
+ time.sleep(10) #@TODO: Remove in 0.4.10 (implement addon delay on startup)
+
+ self.logInfo(_("Proxy listening on %s:%s") % (ip or "0.0.0.0", cnlport))
+
+ self._server(ip, webport, cnlport)
+
+ lock = threading.Lock()
+ lock.acquire()
+ lock.acquire()
+
+
+ @threaded
+ def _server(self, ip, webport, cnlport):
+ try:
+ dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ dock_socket.bind((ip, cnlport))
+ dock_socket.listen(5)
+
+ while True:
+ client_socket, client_addr = dock_socket.accept()
+ self.logDebug("Connection from %s:%s" % client_addr)
+
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ if self.config['webinterface']['https']:
+ try:
+ server_socket = ssl.wrap_socket(server_socket)
+
+ except NameError:
+ self.logError(_("pyLoad's webinterface is configured to use HTTPS, Please install python's ssl lib or disable HTTPS"))
+ client_socket.close() #: reset the connection.
+ continue
+
+ except Exception, e:
+ self.logError(_("SSL error: %s") % e.message)
+ client_socket.close() #: reset the connection.
+ continue
+
+ server_socket.connect(("127.0.0.1", webport))
+
+ self.manager.startThread(forward, client_socket, server_socket)
+ self.manager.startThread(forward, server_socket, client_socket)
+
+ except socket.timeout:
+ self.logDebug("Connection timed out, retrying...")
+ return self._server(ip, webport, cnlport)
+
+ except socket.error, e:
+ self.logError(e)
+ time.sleep(240)
+ return self._server(ip, webport, cnlport)
diff --git a/pyload/plugin/addon/DeleteFinished.py b/pyload/plugin/addon/DeleteFinished.py
new file mode 100644
index 000000000..8fe0b1497
--- /dev/null
+++ b/pyload/plugin/addon/DeleteFinished.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+from pyload.database import style
+from pyload.plugin.Addon import Addon
+
+
+class DeleteFinished(Addon):
+ __name = "DeleteFinished"
+ __type = "addon"
+ __version = "1.12"
+
+ __config = [("interval" , "int" , "Check interval in hours" , 72 ),
+ ("deloffline", "bool", "Delete package with offline links", False)]
+
+ __description = """Automatically delete all finished packages from queue"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+ MIN_CHECK_INTERVAL = 1 * 60 * 60 #: 1 hour
+
+
+ ## overwritten methods ##
+
+
+ def setup(self):
+ self.interval = self.MIN_CHECK_INTERVAL
+
+
+ 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 deactivate(self):
+ self.manager.removeEvent('packageFinished', self.wakeup)
+
+
+ def activate(self):
+ self.info['sleep'] = True
+ # interval = self.getConfig('interval')
+ # self.pluginConfigChanged(self.getClassName(), 'interval', interval)
+ self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60 * 60)
+ self.addEvent('packageFinished', self.wakeup)
+ self.initPeriodical()
+
+
+ ## 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.manager.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.manager.events:
+ if func in self.manager.events[event]:
+ self.logDebug("Function already registered", func)
+ else:
+ self.manager.events[event].append(func)
+ else:
+ self.manager.events[event] = [func]
diff --git a/pyload/plugin/addon/DownloadScheduler.py b/pyload/plugin/addon/DownloadScheduler.py
new file mode 100644
index 000000000..62cee31c5
--- /dev/null
+++ b/pyload/plugin/addon/DownloadScheduler.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.Addon import Addon
+
+
+class DownloadScheduler(Addon):
+ __name = "DownloadScheduler"
+ __type = "addon"
+ __version = "0.22"
+
+ __config = [("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"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def activate(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 = time.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/plugin/addon/ExternalScripts.py b/pyload/plugin/addon/ExternalScripts.py
new file mode 100644
index 000000000..341dd6dcd
--- /dev/null
+++ b/pyload/plugin/addon/ExternalScripts.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+
+import os
+import subprocess
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_encode, fs_join
+
+
+class ExternalScripts(Addon):
+ __name = "ExternalScripts"
+ __type = "addon"
+ __version = "0.39"
+
+ __config = [("activated", "bool", "Activated" , True),
+ ("waitend" , "bool", "Wait script ending", False)]
+
+ __description = """Run external scripts"""
+ __license = "GPLv3"
+ __authors = [("mkaay" , "mkaay@mkaay.de"),
+ ("RaNaN" , "ranan@pyload.org"),
+ ("spoob" , "spoob@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+ event_list = ["archive_extract_failed", "archive_extracted" ,
+ "package_extract_failed", "package_extracted" ,
+ "all_archives_extracted", "all_archives_processed",
+ "allDownloadsFinished" , "allDownloadsProcessed" ,
+ "packageDeleted"]
+
+
+ def setup(self):
+ self.info['oldip'] = None
+ self.scripts = {}
+
+ folders = ["pyload_start", "pyload_restart", "pyload_stop",
+ "before_reconnect", "after_reconnect",
+ "download_preparing", "download_failed", "download_finished",
+ "archive_extract_failed", "archive_extracted",
+ "package_finished", "package_deleted", "package_extract_failed", "package_extracted",
+ "all_downloads_processed", "all_downloads_finished", #@TODO: Invert `all_downloads_processed`, `all_downloads_finished` order in 0.4.10
+ "all_archives_extracted", "all_archives_processed"]
+
+ for folder in folders:
+ self.scripts[folder] = []
+ for dir in (pypath, ''):
+ self.initPluginType(folder, os.path.join(dir, 'scripts', folder))
+
+ for script_type, names in self.scripts.iteritems():
+ if names:
+ self.logInfo(_("Installed scripts for: ") + script_type, ", ".join(map(os.path.basename, names)))
+
+ self.pyload_start()
+
+
+ def initPluginType(self, name, dir):
+ if not os.path.isdir(dir):
+ try:
+ os.makedirs(dir)
+
+ except OSError, e:
+ self.logDebug(e)
+ return
+
+ for filename in os.listdir(dir):
+ file = fs_join(dir, filename)
+
+ if not os.path.isfile(file):
+ continue
+
+ if filename[0] in ("#", "_") or filename.endswith("~") or filename.endswith(".swp"):
+ continue
+
+ if not os.access(file, os.X_OK):
+ self.logWarning(_("Script not executable:") + " %s/%s" % (name, filename))
+
+ self.scripts[name].append(file)
+
+
+ def callScript(self, script, *args):
+ try:
+ cmd_args = [fs_encode(str(x) if not isinstance(x, basestring) else x) for x in args]
+ cmd = [script] + cmd_args
+
+ self.logDebug("Executing: %s" % os.path.abspath(script), "Args: " + ' '.join(cmd_args))
+
+ p = subprocess.Popen(cmd, bufsize=-1) #@NOTE: output goes to pyload
+ if self.getConfig('waitend'):
+ p.communicate()
+
+ except Exception, e:
+ try:
+ self.logError(_("Runtime error: %s") % os.path.abspath(script), e)
+ except Exception:
+ self.logError(_("Runtime error: %s") % os.path.abspath(script), _("Unknown error"))
+
+
+ def pyload_start(self):
+ for script in self.scripts['pyload_start']:
+ self.callScript(script)
+
+
+ def exit(self):
+ for script in self.scripts['pyload_restart' if self.core.do_restart else 'pyload_stop']:
+ self.callScript(script)
+
+
+ def beforeReconnecting(self, ip):
+ for script in self.scripts['before_reconnect']:
+ self.callScript(script, ip)
+ self.info['oldip'] = ip
+
+
+ def afterReconnecting(self, ip):
+ for script in self.scripts['after_reconnect']:
+ self.callScript(script, ip, self.info['oldip']) #@TODO: Use built-in oldip in 0.4.10
+
+
+ def downloadPreparing(self, pyfile):
+ for script in self.scripts['download_preparing']:
+ self.callScript(script, pyfile.id, pyfile.name, None, pyfile.pluginname, pyfile.url)
+
+
+ def downloadFailed(self, pyfile):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pyfile.package().folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['download_failed']:
+ file = fs_join(download_folder, pyfile.name)
+ self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
+
+
+ def downloadFinished(self, pyfile):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pyfile.package().folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['download_finished']:
+ file = fs_join(download_folder, pyfile.name)
+ self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url)
+
+
+ def archive_extract_failed(self, pyfile, archive):
+ for script in self.scripts['archive_extract_failed']:
+ self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
+
+
+ def archive_extracted(self, pyfile, archive):
+ for script in self.scripts['archive_extracted']:
+ self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files)
+
+
+ def packageFinished(self, pypack):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_finished']:
+ self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
+
+
+ def packageDeleted(self, pid):
+ pack = self.core.api.getPackageInfo(pid)
+
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_deleted']:
+ self.callScript(script, pack.id, pack.name, download_folder, pack.password)
+
+
+ def package_extract_failed(self, pypack):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_extract_failed']:
+ self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password)
+
+
+ def package_extracted(self, pypack):
+ if self.core.config.get("general", "folder_per_package"):
+ download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder)
+ else:
+ download_folder = self.core.config.get("general", "download_folder")
+
+ for script in self.scripts['package_extracted']:
+ self.callScript(script, pypack.id, pypack.name, download_folder)
+
+
+ def allDownloadsFinished(self):
+ for script in self.scripts['all_downloads_finished']:
+ self.callScript(script)
+
+
+ def allDownloadsProcessed(self):
+ for script in self.scripts['all_downloads_processed']:
+ self.callScript(script)
+
+
+ def all_archives_extracted(self):
+ for script in self.scripts['all_archives_extracted']:
+ self.callScript(script)
+
+
+ def all_archives_processed(self):
+ for script in self.scripts['all_archives_processed']:
+ self.callScript(script)
diff --git a/pyload/plugin/addon/ExtractArchive.py b/pyload/plugin/addon/ExtractArchive.py
new file mode 100644
index 000000000..53bf5559d
--- /dev/null
+++ b/pyload/plugin/addon/ExtractArchive.py
@@ -0,0 +1,571 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import sys
+import traceback
+
+# 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
+ import subprocess
+
+
+ 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
+
+ subprocess.Popen.wait = wait
+
+try:
+ import send2trash
+except ImportError:
+ pass
+
+if os.name != "nt":
+ from grp import getgrnam
+ from pwd import getpwnam
+
+from pyload.plugin.Addon import Addon, threaded, Expose
+from pyload.plugin.Extractor import ArchiveError, CRCError, PasswordError
+from pyload.plugin.internal.SimpleHoster import replace_patterns
+from pyload.utils import fs_encode, fs_join, uniqify
+
+
+class ArchiveQueue(object):
+
+ def __init__(self, plugin, storage):
+ self.plugin = plugin
+ self.storage = storage
+
+
+ def get(self):
+ try:
+ return [int(pid) for pid in self.plugin.getStorage("ExtractArchive:%s" % self.storage, "").decode('base64').split()]
+ except Exception:
+ return []
+
+
+ def set(self, value):
+ if isinstance(value, list):
+ item = str(value)[1:-1].replace(' ', '').replace(',', ' ')
+ else:
+ item = str(value).strip()
+ return self.plugin.setStorage("ExtractArchive:%s" % self.storage, item.encode('base64')[:-1])
+
+
+ def delete(self):
+ return self.plugin.delStorage("ExtractArchive:%s" % self.storage)
+
+
+ def add(self, item):
+ queue = self.get()
+ if item not in queue:
+ return self.set(queue + [item])
+ else:
+ return True
+
+
+ def remove(self, item):
+ queue = self.get()
+ try:
+ queue.remove(item)
+
+ except ValueError:
+ pass
+
+ if queue == []:
+ return self.delete()
+
+ return self.set(queue)
+
+
+class ExtractArchive(Addon):
+ __name = "ExtractArchive"
+ __type = "addon"
+ __version = "1.44"
+
+ __config = [("activated" , "bool" , "Activated" , True),
+ ("fullpath" , "bool" , "Extract with full paths" , True),
+ ("overwrite" , "bool" , "Overwrite files" , False),
+ ("keepbroken" , "bool" , "Try to extract broken archives" , False),
+ ("repair" , "bool" , "Repair broken archives (RAR required)" , False),
+ ("test" , "bool" , "Test archive before extracting" , False),
+ ("usepasswordfile", "bool" , "Use password file" , True),
+ ("passwordfile" , "file" , "Password file" , "archive_password.txt"),
+ ("delete" , "bool" , "Delete archive after extraction" , True),
+ ("deltotrash" , "bool" , "Move to trash (recycle bin) instead delete", True),
+ ("subfolder" , "bool" , "Create subfolder for each package" , False),
+ ("destination" , "folder" , "Extract files to folder" , ""),
+ ("extensions" , "str" , "Extract archives ending with extension" , "7z,bz2,bzip2,gz,gzip,lha,lzh,lzma,rar,tar,taz,tbz,tbz2,tgz,xar,xz,z,zip"),
+ ("excludefiles" , "str" , "Don't extract the following files" , "*.nfo,*.DS_Store,index.dat,thumb.db"),
+ ("recursive" , "bool" , "Extract archives in archives" , True),
+ ("waitall" , "bool" , "Run after all downloads was processed" , False),
+ ("renice" , "int" , "CPU priority" , 0)]
+
+ __description = """Extract different kind of archives"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("Immenz" , "immenz@gmx.net")]
+
+
+ event_list = ["allDownloadsProcessed", "packageDeleted"]
+
+ NAME_REPLACEMENTS = [(r'\.part\d+\.rar$', ".part.rar")]
+
+
+ def setup(self):
+ self.queue = ArchiveQueue(self, "Queue.Queue")
+ self.failed = ArchiveQueue(self, "Failed")
+
+ self.interval = 60
+ self.extracting = False
+ self.lastPackage = False
+ self.extractors = []
+ self.passwords = []
+ self.repair = False
+
+
+ def activate(self):
+ for p in ("UnRar", "SevenZip", "UnZip"):
+ try:
+ module = self.core.pluginManager.loadModule("extractor", p)
+ klass = getattr(module, p)
+ if klass.isUsable():
+ self.extractors.append(klass)
+ if klass.REPAIR:
+ self.repair = self.getConfig('repair')
+
+ except OSError, e:
+ if e.errno == 2:
+ self.logWarning(_("No %s installed") % p)
+ else:
+ self.logWarning(_("Could not activate: %s") % p, e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ except Exception, e:
+ self.logWarning(_("Could not activate: %s") % p, e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ if self.extractors:
+ self.logDebug(*["Found %s %s" % (Extractor.NAME, Extractor.VERSION) for Extractor in self.extractors])
+ self.extractQueued() #: Resume unfinished extractions
+ else:
+ self.logInfo(_("No Extract plugins activated"))
+
+
+ @threaded
+ def extractQueued(self, thread):
+ if self.extracting: #@NOTE: doing the check here for safty (called by coreReady)
+ return
+
+ self.extracting = True
+
+ packages = self.queue.get()
+ while packages:
+ if self.lastPackage: #: called from allDownloadsProcessed
+ self.lastPackage = False
+ if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
+ self.manager.dispatchEvent("all_archives_extracted")
+ self.manager.dispatchEvent("all_archives_processed")
+ else:
+ if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now
+ pass
+
+ packages = self.queue.get() #: check for packages added during extraction
+
+ self.extracting = False
+
+
+ @Expose
+ def extractPackage(self, *ids):
+ """ Extract packages with given id"""
+ for id in ids:
+ self.queue.add(id)
+ if not self.getConfig('waitall') and not self.extracting:
+ self.extractQueued()
+
+
+ def packageDeleted(self, pid):
+ self.queue.remove(pid)
+
+
+ def packageFinished(self, pypack):
+ self.queue.add(pypack.id)
+ if not self.getConfig('waitall') and not self.extracting:
+ self.extractQueued()
+
+
+ def allDownloadsProcessed(self):
+ self.lastPackage = True
+ if self.getConfig('waitall') and not self.extracting:
+ self.extractQueued()
+
+
+ @Expose
+ def extract(self, ids, thread=None): #@TODO: Use pypack, not pid to improve method usability
+ if not ids:
+ return False
+
+ processed = []
+ extracted = []
+ failed = []
+
+ toList = lambda string: string.replace(' ', '').replace(',', '|').replace(';', '|').split('|')
+
+ destination = self.getConfig('destination')
+ subfolder = self.getConfig('subfolder')
+ fullpath = self.getConfig('fullpath')
+ overwrite = self.getConfig('overwrite')
+ renice = self.getConfig('renice')
+ recursive = self.getConfig('recursive')
+ delete = self.getConfig('delete')
+ keepbroken = self.getConfig('keepbroken')
+
+ extensions = [x.lstrip('.').lower() for x in toList(self.getConfig('extensions'))]
+ excludefiles = toList(self.getConfig('excludefiles'))
+
+ if extensions:
+ self.logDebug("Use for extensions: %s" % "|.".join(extensions))
+
+ # reload from txt file
+ self.reloadPasswords()
+
+ download_folder = self.config.get("general", "download_folder")
+
+ # iterate packages -> extractors -> targets
+ for pid in ids:
+ pypack = self.core.files.getPackage(pid)
+
+ if not pypack:
+ self.queue.remove(pid)
+ continue
+
+ self.logInfo(_("Check package: %s") % pypack.name)
+
+ # determine output folder
+ out = fs_join(download_folder, pypack.folder, destination, "") #: force trailing slash
+
+ if subfolder:
+ out = fs_join(out, pypack.folder)
+
+ if not os.path.exists(out):
+ os.makedirs(out)
+
+ matched = False
+ success = True
+ files_ids = dict((pylink['name'], ((fs_join(download_folder, pypack.folder, pylink['name'])), pylink['id'], out)) for pylink
+ in sorted(pypack.getChildren().itervalues(), key=lambda k: k['name'])).values() #: remove duplicates
+
+ # check as long there are unseen files
+ while files_ids:
+ new_files_ids = []
+
+ if extensions:
+ files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids
+ if filter(lambda ext: fname.lower().endswith(ext), extensions)]
+
+ for Extractor in self.extractors:
+ targets = Extractor.getTargets(files_ids)
+ if targets:
+ self.logDebug("Targets for %s: %s" % (Extractor.__name__, targets))
+ matched = True
+
+ for fname, fid, fout in targets:
+ name = os.path.basename(fname)
+
+ if not os.path.exists(fname):
+ self.logDebug(name, "File not found")
+ continue
+
+ self.logInfo(name, _("Extract to: %s") % fout)
+ try:
+ pyfile = self.core.files.getFile(fid)
+ archive = Extractor(self,
+ fname,
+ fout,
+ fullpath,
+ overwrite,
+ excludefiles,
+ renice,
+ delete,
+ keepbroken,
+ fid)
+
+ thread.addActive(pyfile)
+ archive.init()
+
+ try:
+ new_files = self._extract(pyfile, archive, pypack.password)
+
+ finally:
+ pyfile.setProgress(100)
+ thread.finishFile(pyfile)
+
+ except Exception, e:
+ self.logError(name, e)
+ success = False
+ continue
+
+ # remove processed file and related multiparts from list
+ files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids
+ if fname not in archive.getDeleteFiles()]
+ self.logDebug("Extracted files: %s" % new_files)
+ self.setPermissions(new_files)
+
+ for filename in new_files:
+ file = fs_encode(fs_join(os.path.dirname(archive.filename), filename))
+ if not os.path.exists(file):
+ self.logDebug("New file %s does not exists" % filename)
+ continue
+
+ if recursive and os.path.isfile(file):
+ new_files_ids.append((filename, fid, os.path.dirname(filename))) #: append as new target
+
+ self.manager.dispatchEvent("archive_extracted", pyfile, archive)
+
+ files_ids = new_files_ids #: also check extracted files
+
+ if matched:
+ if success:
+ extracted.append(pid)
+ self.manager.dispatchEvent("package_extracted", pypack)
+
+ else:
+ failed.append(pid)
+ self.manager.dispatchEvent("package_extract_failed", pypack)
+
+ self.failed.add(pid)
+ else:
+ self.logInfo(_("No files found to extract"))
+
+ if not matched or not success and subfolder:
+ try:
+ os.rmdir(out)
+
+ except OSError:
+ pass
+
+ self.queue.remove(pid)
+
+ return True if not failed else False
+
+
+ def _extract(self, pyfile, archive, password):
+ name = os.path.basename(archive.filename)
+
+ pyfile.setStatus("processing")
+
+ encrypted = False
+ try:
+ self.logDebug("Password: %s" % (password or "None provided"))
+ passwords = uniqify([password] + self.getPasswords(False)) if self.getConfig('usepasswordfile') else [password]
+ for pw in passwords:
+ try:
+ if self.getConfig('test') or self.repair:
+ pyfile.setCustomStatus(_("archive testing"))
+ if pw:
+ self.logDebug("Testing with password: %s" % pw)
+ pyfile.setProgress(0)
+ archive.verify(pw)
+ pyfile.setProgress(100)
+ else:
+ archive.check(pw)
+
+ self.addPassword(pw)
+ break
+
+ except PasswordError:
+ if not encrypted:
+ self.logInfo(name, _("Password protected"))
+ encrypted = True
+
+ except CRCError, e:
+ self.logDebug(name, e)
+ self.logInfo(name, _("CRC Error"))
+
+ if self.repair:
+ self.logWarning(name, _("Repairing..."))
+
+ pyfile.setCustomStatus(_("archive repairing"))
+ pyfile.setProgress(0)
+ repaired = archive.repair()
+ pyfile.setProgress(100)
+
+ if not repaired and not self.getConfig('keepbroken'):
+ raise CRCError("Archive damaged")
+
+ self.addPassword(pw)
+ break
+
+ raise CRCError("Archive damaged")
+
+ except ArchiveError, e:
+ raise ArchiveError(e)
+
+ pyfile.setCustomStatus(_("extracting"))
+ pyfile.setProgress(0)
+
+ if not encrypted or not self.getConfig('usepasswordfile'):
+ self.logDebug("Extracting using password: %s" % (password or "None"))
+ archive.extract(password)
+ else:
+ for pw in filter(None, uniqify([password] + self.getPasswords(False))):
+ try:
+ self.logDebug("Extracting using password: %s" % pw)
+
+ archive.extract(pw)
+ self.addPassword(pw)
+ break
+
+ except PasswordError:
+ self.logDebug("Password was wrong")
+ else:
+ raise PasswordError
+
+ pyfile.setProgress(100)
+ pyfile.setStatus("processing")
+
+ delfiles = archive.getDeleteFiles()
+ self.logDebug("Would delete: " + ", ".join(delfiles))
+
+ if self.getConfig('delete'):
+ self.logInfo(_("Deleting %s files") % len(delfiles))
+
+ deltotrash = self.getConfig('deltotrash')
+ for f in delfiles:
+ file = fs_encode(f)
+ if not os.path.exists(file):
+ continue
+
+ if not deltotrash:
+ os.remove(file)
+
+ else:
+ try:
+ send2trash.send2trash(file)
+
+ except NameError:
+ self.logWarning(_("Unable to move %s to trash: Send2Trash lib not found") % os.path.basename(f))
+
+ except Exception, e:
+ self.logWarning(_("Unable to move %s to trash: %s") % (os.path.basename(f), e.message))
+
+ else:
+ self.logDebug(_("Successfully moved %s to trash") % os.path.basename(f))
+
+ self.logInfo(name, _("Extracting finished"))
+ extracted_files = archive.files or archive.list()
+
+ return extracted_files
+
+ except PasswordError:
+ self.logError(name, _("Wrong password" if password else "No password found"))
+
+ except CRCError, e:
+ self.logError(name, _("CRC mismatch"), e)
+
+ except ArchiveError, e:
+ self.logError(name, _("Archive error"), e)
+
+ except Exception, e:
+ self.logError(name, _("Unknown error"), e)
+ if self.core.debug:
+ traceback.print_exc()
+
+ self.manager.dispatchEvent("archive_extract_failed", pyfile, archive)
+
+ raise Exception(_("Extract failed"))
+
+
+ @Expose
+ def getPasswords(self, reload=True):
+ """ List of saved passwords """
+ if reload:
+ self.reloadPasswords()
+
+ return self.passwords
+
+
+ def reloadPasswords(self):
+ try:
+ passwords = []
+
+ file = fs_encode(self.getConfig('passwordfile'))
+ with open(file) as f:
+ for pw in f.read().splitlines():
+ passwords.append(pw)
+
+ except IOError, e:
+ self.logError(e)
+
+ else:
+ self.passwords = passwords
+
+
+ @Expose
+ def addPassword(self, password):
+ """ Adds a password to saved list"""
+ try:
+ self.passwords = uniqify([password] + self.passwords)
+
+ file = fs_encode(self.getConfig('passwordfile'))
+ with open(file, "wb") as f:
+ for pw in self.passwords:
+ f.write(pw + '\n')
+
+ except IOError, e:
+ self.logError(e)
+
+
+ def setPermissions(self, files):
+ for f in files:
+ if not os.path.exists(f):
+ continue
+
+ try:
+ if self.config.get("permission", "change_file"):
+ if os.path.isfile(f):
+ os.chmod(f, int(self.config.get("permission", "file"), 8))
+
+ elif os.path.isdir(f):
+ os.chmod(f, int(self.config.get("permission", "folder"), 8))
+
+ if self.config.get("permission", "change_dl") and os.name != "nt":
+ uid = getpwnam(self.config.get("permission", "user"))[2]
+ gid = getgrnam(self.config.get("permission", "group"))[2]
+ os.chown(f, uid, gid)
+
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed"), e)
diff --git a/pyload/plugin/addon/HotFolder.py b/pyload/plugin/addon/HotFolder.py
new file mode 100644
index 000000000..f7f543c3a
--- /dev/null
+++ b/pyload/plugin/addon/HotFolder.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import shutil
+import time
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_encode, fs_join
+
+
+class HotFolder(Addon):
+ __name = "HotFolder"
+ __type = "addon"
+ __version = "0.14"
+
+ __config = [("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"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.de")]
+
+
+ def setup(self):
+ self.interval = 30
+
+
+ def activate(self):
+ self.initPeriodical()
+
+
+ def periodical(self):
+ folder = fs_encode(self.getConfig('folder'))
+ file = fs_encode(self.getConfig('file'))
+
+ try:
+ if not os.path.isdir(os.path.join(folder, "finished")):
+ os.makedirs(os.path.join(folder, "finished"))
+
+ if self.getConfig('watch_file'):
+ with open(file, "a+") as f:
+ f.seek(0)
+ content = f.read().strip()
+
+ if content:
+ f = open(file, "wb")
+ f.close()
+
+ name = "%s_%s.txt" % (file, time.strftime("%H-%M-%S_%d%b%Y"))
+
+ with open(fs_join(folder, "finished", name), "wb") as f:
+ f.write(content)
+
+ self.core.api.addPackage(f.name, [f.name], 1)
+
+ for f in os.listdir(folder):
+ path = os.path.join(folder, f)
+
+ if not os.path.isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."):
+ continue
+
+ newpath = os.path.join(folder, "finished", f if self.getConfig('keep') else "tmp_" + f)
+ shutil.move(path, newpath)
+
+ self.logInfo(_("Added %s from HotFolder") % f)
+ self.core.api.addPackage(f, [newpath], 1)
+
+ except (IOError, OSError), e:
+ self.logError(e)
diff --git a/pyload/plugin/addon/IRCInterface.py b/pyload/plugin/addon/IRCInterface.py
new file mode 100644
index 000000000..0c8f2ce8b
--- /dev/null
+++ b/pyload/plugin/addon/IRCInterface.py
@@ -0,0 +1,431 @@
+# -*- coding: utf-8 -*-
+
+import re
+import select
+import socket
+import ssl
+import time
+import threading
+import traceback
+
+import pycurl
+
+from pyload.Api import PackageDoesNotExists, FileDoesNotExists
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Addon import Addon
+from pyload.utils import formatSize
+
+
+class IRCInterface(Thread, Addon):
+ __name = "IRCInterface"
+ __type = "addon"
+ __version = "0.13"
+
+ __config = [("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" ),
+ ("ssl" , "bool", "Use SSL" , False ),
+ ("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"""
+ __license = "GPLv3"
+ __authors = [("Jeix", "Jeix@hasnomail.com")]
+
+
+ def __init__(self, core, manager):
+ threading.Thread.__init__(self)
+ Addon.__init__(self, core, manager)
+ self.setDaemon(True)
+
+
+ def activate(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 Exception:
+ 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 Exception:
+ pass
+
+
+ def captchaTask(self, task):
+ if self.getConfig('captcha') and task.isTextual():
+ task.handler.append(self)
+ task.setWaiting(60)
+
+ html = getURL("http://www.freeimagehosting.net/upload.php",
+ post={"attached": (pycurl.FORM_FILE, task.captchaFile)}, multipart=True)
+
+ url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", html).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')))
+
+ if self.getConfig('ssl'):
+ self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE) #@TODO: support certificate
+
+ 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(_("Connected to"), host)
+ self.logInfo(_("Switching to listening mode!"))
+ try:
+ self.main_loop()
+
+ except IRCError, ex:
+ self.sock.send("QUIT :byebye\r\n")
+ traceback.print_exc()
+ self.sock.close()
+
+
+ def main_loop(self):
+ readbuffer = ""
+ while True:
+ time.sleep(1)
+ fdset = select.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 Exception:
+ 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(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.core.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.core.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.core.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.core.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.core.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.core.api.unpauseServer()
+ return ["INFO: Starting downloads."]
+
+
+ def event_stop(self, args):
+ self.core.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.core.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 Exception:
+ # create new package
+ id = self.core.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.core.api.deletePackages(map(int, args[1:]))
+ return ["INFO: Deleted %d packages!" % len(args[1:])]
+
+ elif args[0] == "-l":
+ ret = self.core.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.core.api.getPackageInfo(id)
+ except PackageDoesNotExists:
+ return ["ERROR: Package #%d does not exist." % id]
+
+ self.core.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.core.api.getPackageData(id):
+ return ["ERROR: Package #%d does not exist." % id]
+
+ self.core.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/plugin/addon/JustPremium.py b/pyload/plugin/addon/JustPremium.py
new file mode 100644
index 000000000..03a2b9261
--- /dev/null
+++ b/pyload/plugin/addon/JustPremium.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Addon import Addon
+
+
+class JustPremium(Addon):
+ __name = "JustPremium"
+ __type = "addon"
+ __version = "0.22"
+
+ __config = [("excluded", "str", "Exclude hosters (comma separated)", ""),
+ ("included", "str", "Include hosters (comma separated)", "")]
+
+ __description = """Remove not-premium links from added urls"""
+ __license = "GPLv3"
+ __authors = [("mazleu" , "mazleica@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("immenz" , "immenz@gmx.net" )]
+
+
+ event_list = ["linksAdded"]
+
+
+ def linksAdded(self, links, pid):
+ hosterdict = self.core.pluginManager.hosterPlugins
+ linkdict = self.core.api.checkURLs(links)
+
+ premiumplugins = set(account.type for account in self.core.api.getAccounts(False) \
+ if account.valid and account.premium)
+ multihosters = set(hoster for hoster in self.core.pluginManager.hosterPlugins \
+ if 'new_name' in hosterdict[hoster] \
+ and hosterdict[hoster]['new_name'] in premiumplugins)
+
+ excluded = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
+ self.getConfig('excluded').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
+ included = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'),
+ self.getConfig('included').replace(' ', '').replace(',', '|').replace(';', '|').split('|'))
+
+ hosterlist = (premiumplugins | multihosters).union(excluded).difference(included)
+
+ #: Found at least one hoster with account or multihoster
+ if not any( True for pluginname in linkdict if pluginname in hosterlist ):
+ return
+
+ for pluginname in set(linkdict.keys()) - hosterlist:
+ self.logInfo(_("Remove links of plugin: %s") % pluginname)
+ for link in linkdict[pluginname]:
+ self.logDebug("Remove link: %s" % link)
+ links.remove(link)
diff --git a/pyload/plugin/addon/MergeFiles.py b/pyload/plugin/addon/MergeFiles.py
new file mode 100644
index 000000000..4f95ef9d7
--- /dev/null
+++ b/pyload/plugin/addon/MergeFiles.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+import traceback
+
+from pyload.plugin.Addon import Addon, threaded
+from pyload.utils import fs_join
+
+
+class MergeFiles(Addon):
+ __name = "MergeFiles"
+ __type = "addon"
+ __version = "0.14"
+
+ __config = [("activated", "bool", "Activated", True)]
+
+ __description = """Merges parts splitted with hjsplit"""
+ __license = "GPLv3"
+ __authors = [("and9000", "me@has-no-mail.com")]
+
+
+ BUFFER_SIZE = 4096
+
+
+ @threaded
+ def packageFinished(self, pack):
+ files = {}
+ fid_dict = {}
+ for fid, data in pack.getChildren().iteritems():
+ if re.search("\.\d{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.get("general", "download_folder")
+
+ if self.config.get("general", "folder_per_package"):
+ download_folder = fs_join(download_folder, pack.folder)
+
+ for name, file_list in files.iteritems():
+ self.logInfo(_("Starting merging of"), name)
+
+ with open(fs_join(download_folder, name), "wb") as final_file:
+ for splitted_file in file_list:
+ self.logDebug("Merging part", splitted_file)
+
+ pyfile = self.core.files.getFile(fid_dict[splitted_file])
+
+ pyfile.setStatus("processing")
+
+ try:
+ with open(fs_join(download_folder, splitted_file), "rb") as s_file:
+ 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
+
+ self.logDebug("Finished merging part", splitted_file)
+
+ except Exception, e:
+ traceback.print_exc()
+
+ finally:
+ pyfile.setProgress(100)
+ pyfile.setStatus("finished")
+ pyfile.release()
+
+ self.logInfo(_("Finished merging of"), name)
diff --git a/pyload/plugin/addon/MultiHome.py b/pyload/plugin/addon/MultiHome.py
new file mode 100644
index 000000000..bca6493db
--- /dev/null
+++ b/pyload/plugin/addon/MultiHome.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.plugin.Addon import Addon
+
+
+class MultiHome(Addon):
+ __name = "MultiHome"
+ __type = "addon"
+ __version = "0.12"
+
+ __config = [("interfaces", "str", "Interfaces", "None")]
+
+ __description = """Ip address changer"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
+
+
+ def setup(self):
+ self.register = {}
+ self.interfaces = []
+
+ self.parseInterfaces(self.getConfig('interfaces').split(";"))
+
+ if not self.interfaces:
+ self.parseInterfaces([self.config.get("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 activate(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("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.time()
+
+
+ def __repr__(self):
+ return "<Interface - %s>" % self.adress
diff --git a/pyload/plugin/addon/RestartFailed.py b/pyload/plugin/addon/RestartFailed.py
new file mode 100644
index 000000000..8a3a36fe1
--- /dev/null
+++ b/pyload/plugin/addon/RestartFailed.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Addon import Addon
+
+
+class RestartFailed(Addon):
+ __name = "RestartFailed"
+ __type = "addon"
+ __version = "1.58"
+
+ __config = [("activated", "bool", "Activated" , True),
+ ("interval" , "int" , "Check interval in minutes", 90 )]
+
+ __description = """Restart all the failed downloads in queue"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+
+ MIN_CHECK_INTERVAL = 15 * 60 #: 15 minutes
+
+
+ # def pluginConfigChanged(self, plugin, name, value):
+ # if name == "interval":
+ # interval = value * 60
+ # if self.MIN_CHECK_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.logDebug(_("Restart failed downloads"))
+ self.core.api.restartFailed()
+
+
+ def activate(self):
+ # self.pluginConfigChanged(self.getClassName(), "interval", self.getConfig('interval'))
+ self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60)
+ self.initPeriodical()
diff --git a/pyload/plugin/addon/SkipRev.py b/pyload/plugin/addon/SkipRev.py
new file mode 100644
index 000000000..b19650710
--- /dev/null
+++ b/pyload/plugin/addon/SkipRev.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+import urlparse
+import types
+
+from pyload.datatype.File import PyFile
+from pyload.plugin.Addon import Addon
+from pyload.plugin.Plugin import SkipDownload
+
+
+class SkipRev(Addon):
+ __name = "SkipRev"
+ __type = "addon"
+ __version = "0.29"
+
+ __config = [("mode" , "Auto;Manual", "Choose recovery archives to skip" , "Auto"),
+ ("revtokeep", "int" , "Number of recovery archives to keep for package", 0 )]
+
+ __description = """Skip recovery archives (.rev)"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ @staticmethod
+ def _setup(self):
+ self.pyfile.plugin._setup()
+ if self.pyfile.hasStatus("skipped"):
+ raise SkipDownload(self.pyfile.statusname or self.pyfile.pluginname)
+
+
+ def _name(self, pyfile):
+ if hasattr(pyfile.pluginmodule, "getInfo"): #@NOTE: getInfo is deprecated in 0.4.10
+ return pyfile.pluginmodule.getInfo([pyfile.url]).next()[0]
+ else:
+ self.logWarning("Unable to grab file name")
+ return urlparse.urlparse(urllib.unquote(pyfile.url)).path.split('/')[-1]
+
+
+ def _pyfile(self, link):
+ return PyFile(self.core.files,
+ link.fid,
+ link.url,
+ link.name,
+ link.size,
+ link.status,
+ link.error,
+ link.plugin,
+ link.packageID,
+ link.order)
+
+
+ def downloadPreparing(self, pyfile):
+ name = self._name(pyfile)
+
+ if pyfile.statusname is _("unskipped") or not name.endswith(".rev") or not ".part" in name:
+ return
+
+ revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
+
+ if revtokeep:
+ status_list = (1, 4, 8, 9, 14) if revtokeep < 0 else (1, 3, 4, 8, 9, 14)
+ pyname = re.compile(r'%s\.part\d+\.rev$' % name.rsplit('.', 2)[0].replace('.', '\.'))
+
+ queued = [True for link in self.core.api.getPackageData(pyfile.package().id).links \
+ if link.status not in status_list and pyname.match(link.name)].count(True)
+
+ if not queued or queued < revtokeep: #: keep one rev at least in auto mode
+ return
+
+ pyfile.setCustomStatus("SkipRev", "skipped")
+
+ if not hasattr(pyfile.plugin, "_setup"):
+ # Work-around: inject status checker inside the preprocessing routine of the plugin
+ pyfile.plugin._setup = pyfile.plugin.setup
+ pyfile.plugin.setup = types.MethodType(self._setup, pyfile.plugin)
+
+
+ def downloadFailed(self, pyfile):
+ #: Check if pyfile is still "failed",
+ # maybe might has been restarted in meantime
+ if pyfile.status != 8 or pyfile.name.rsplit('.', 1)[-1].strip() not in ("rar", "rev"):
+ return
+
+ revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep')
+
+ if not revtokeep:
+ return
+
+ pyname = re.compile(r'%s\.part\d+\.rev$' % pyfile.name.rsplit('.', 2)[0].replace('.', '\.'))
+
+ for link in self.core.api.getPackageData(pyfile.package().id).links:
+ if link.status is 4 and pyname.match(link.name):
+ pylink = self._pyfile(link)
+
+ if revtokeep > -1 or pyfile.name.endswith(".rev"):
+ pylink.setStatus("queued")
+ else:
+ pylink.setCustomStatus(_("unskipped"), "queued")
+
+ self.core.files.save()
+ pylink.release()
+ return
diff --git a/pyload/plugin/addon/UnSkipOnFail.py b/pyload/plugin/addon/UnSkipOnFail.py
new file mode 100644
index 000000000..048547a1b
--- /dev/null
+++ b/pyload/plugin/addon/UnSkipOnFail.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+from pyload.datatype.File import PyFile
+from pyload.plugin.Addon import Addon
+
+
+class UnSkipOnFail(Addon):
+ __name = "UnSkipOnFail"
+ __type = "addon"
+ __version = "0.07"
+
+ __config = [("activated", "bool", "Activated", True)]
+
+ __description = """Restart skipped duplicates when download fails"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def downloadFailed(self, pyfile):
+ #: Check if pyfile is still "failed",
+ # maybe might has been restarted in meantime
+ if pyfile.status != 8:
+ return
+
+ msg = _("Looking for skipped duplicates of: %s (pid:%s)")
+ self.logInfo(msg % (pyfile.name, pyfile.package().id))
+
+ link = self.findDuplicate(pyfile)
+ if link:
+ self.logInfo(_("Queue found duplicate: %s (pid:%s)") % (link.name, link.packageID))
+
+ #: 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.
+ pylink = self._pyfile(link)
+
+ pylink.setCustomStatus(_("unskipped"), "queued")
+
+ self.core.files.save()
+ pylink.release()
+
+ else:
+ self.logInfo(_("No duplicates found"))
+
+
+ def findDuplicate(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 (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.
+ """
+ queue = self.core.api.getQueue() #: get packages (w/o files, as most file data is useless here)
+
+ for package in queue:
+ #: check if package-folder equals pyfile's package folder
+ if package.folder != pyfile.package().folder:
+ continue
+
+ #: now get packaged data w/ files/links
+ pdata = self.core.api.getPackageData(package.pid)
+ for link in pdata.links:
+ #: check if link is "skipped"
+ if link.status != 4:
+ continue
+
+ #: check if link name collides with pdata's name
+ #: AND at last check if it is not pyfile itself
+ if link.name == pyfile.name and link.fid != pyfile.id:
+ return link
+
+
+ def _pyfile(self, link):
+ return PyFile(self.core.files,
+ link.fid,
+ link.url,
+ link.name,
+ link.size,
+ link.status,
+ link.error,
+ link.plugin,
+ link.packageID,
+ link.order)
diff --git a/pyload/plugin/addon/UpdateManager.py b/pyload/plugin/addon/UpdateManager.py
new file mode 100644
index 000000000..ec147a29a
--- /dev/null
+++ b/pyload/plugin/addon/UpdateManager.py
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import operator
+import os
+import re
+import sys
+import time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Addon import Expose, Addon, threaded
+from pyload.utils import fs_join
+from pyload import __status_code__ as release_status
+
+
+# Case-sensitive os.path.exists
+def exists(path):
+ if os.path.exists(path):
+ if os.name == 'nt':
+ dir, name = os.path.split(path)
+ return name in os.listdir(dir)
+ else:
+ return True
+ else:
+ return False
+
+
+class UpdateManager(Addon):
+ __name = "UpdateManager"
+ __type = "addon"
+ __version = "0.51"
+
+ __config = [("activated", "bool", "Activated", False),
+ ("checkinterval", "int", "Check interval in hours", 8),
+ ("autorestart", "bool",
+ "Auto-restart pyLoad when required", True),
+ ("checkonstart", "bool", "Check for updates on startup", True),
+ ("checkperiod", "bool",
+ "Check for updates periodically", True),
+ ("reloadplugins", "bool",
+ "Monitor plugin code changes in debug mode", True),
+ ("nodebugupdate", "bool", "Don't update plugins in debug mode", False)]
+
+ __description = """ Check for updates """
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ SERVER_URL = "http://updatemanager.pyload.org" if release_status == 5 else None
+ MIN_CHECK_INTERVAL = 3 * 60 * 60 #: 3 hours
+
+ event_list = ["allDownloadsProcessed"]
+
+
+ def activate(self):
+ if self.checkonstart:
+ self.update()
+
+ self.initPeriodical()
+
+
+ def setup(self):
+ self.interval = 10
+ self.info = {'pyload': False, 'version': None, 'plugins': False, 'last_check': time.time()}
+ self.mtimes = {} #: store modification time for each plugin
+
+ if self.getConfig('checkonstart'):
+ self.core.api.pauseServer()
+ self.checkonstart = True
+ else:
+ self.checkonstart = False
+
+ self.do_restart = False
+
+
+ def allDownloadsProcessed(self):
+ if self.do_restart is True:
+ self.logWarning(_("Downloads are done, restarting pyLoad to reload the updated plugins"))
+ self.core.api.restart()
+
+
+ def periodical(self):
+ if self.core.debug:
+ if self.getConfig('reloadplugins'):
+ self.autoreloadPlugins()
+
+ if self.getConfig('nodebugupdate'):
+ return
+
+ if self.getConfig('checkperiod') \
+ and time.time() - max(self.MIN_CHECK_INTERVAL, self.getConfig('checkinterval') * 60 * 60) > self.info['last_check']:
+ self.update()
+
+
+ @Expose
+ def autoreloadPlugins(self):
+ """ reload and reindex all modified plugins """
+ modules = filter(
+ lambda m: m and (m.__name__.startswith("pyload.plugin.") 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 os.path.isfile(f):
+ continue
+
+ mtime = os.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 bool(self.core.pluginManager.reloadPlugins(reloads))
+
+
+ def server_response(self):
+ try:
+ return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
+
+ except Exception:
+ self.logWarning(_("Unable to retrieve server to get updates"))
+
+
+ @Expose
+ @threaded
+ def update(self):
+ """ check for updates """
+
+ self.core.api.pauseServer()
+
+ if self._update() is 2 and self.getConfig('autorestart'):
+ if not self.core.api.statusDownloads():
+ self.core.api.restart()
+ else:
+ self.do_restart = True
+ self.logWarning(_("Downloads are active, will restart once the download is done"))
+ else:
+ self.core.api.unpauseServer()
+
+
+ def _update(self):
+ data = self.server_response()
+
+ self.info['last_check'] = time.time()
+
+ if not data:
+ exitcode = 0
+
+ elif data[0] == "None":
+ self.logInfo(_("No new pyLoad version available"))
+ exitcode = self._updatePlugins(data[1:])
+
+ elif onlyplugin:
+ exitcode = 0
+
+ else:
+ self.logInfo(_("*** New pyLoad Version %s available ***") % data[0])
+ self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
+ self.info['pyload'] = True
+ self.info['version'] = data[0]
+ exitcode = 3
+
+ # Exit codes:
+ # -1 = No plugin updated, new pyLoad version available
+ # 0 = No plugin updated
+ # 1 = Plugins updated
+ # 2 = Plugins updated, but restart required
+ return exitcode
+
+
+ def _updatePlugins(self, data):
+ """ check for plugin updates """
+
+ exitcode = 0
+ updated = []
+
+ url = data[0]
+ schema = data[1].split('|')
+
+ VERSION = re.compile(r'__version.*=.*("|\')([\d.]+)')
+
+ if "BLACKLIST" in data:
+ blacklist = data[data.index('BLACKLIST') + 1:]
+ updatelist = data[2:data.index('BLACKLIST')]
+ else:
+ blacklist = []
+ updatelist = data[2:]
+
+ updatelist = [dict(zip(schema, x.split('|'))) for x in updatelist]
+ blacklist = [dict(zip(schema, x.split('|'))) for x in blacklist]
+
+ if blacklist:
+ type_plugins = [(plugin['type'], plugin['name'].rsplit('.', 1)[0]) for plugin in blacklist]
+
+ # Protect UpdateManager from self-removing
+ try:
+ type_plugins.remove(("addon", "UpdateManager"))
+ except ValueError:
+ pass
+
+ for t, n in type_plugins:
+ for idx, plugin in enumerate(updatelist):
+ if n == plugin['name'] and t == plugin['type']:
+ updatelist.pop(idx)
+ break
+
+ for t, n in self.removePlugins(sorted(type_plugins)):
+ self.logInfo(_("Removed blacklisted plugin: [%(type)s] %(name)s") % {
+ 'type': t,
+ 'name': n,
+ })
+
+ for plugin in sorted(updatelist, key=operator.itemgetter("type", "name")):
+ filename = plugin['name']
+ type = plugin['type']
+ version = plugin['version']
+
+ if filename.endswith(".pyc"):
+ name = filename[:filename.find("_")]
+ else:
+ name = filename.replace(".py", "")
+
+ plugins = getattr(self.core.pluginManager, "%sPlugins" % type)
+
+ oldver = float(plugins[name]['version']) if name in plugins else None
+ newver = float(version)
+
+ if not oldver:
+ msg = "New plugin: [%(type)s] %(name)s (v%(newver).2f)"
+ elif newver > oldver:
+ msg = "New version of plugin: [%(type)s] %(name)s (v%(oldver).2f -> v%(newver).2f)"
+ else:
+ continue
+
+ self.logInfo(_(msg) % {'type': type,
+ 'name': name,
+ 'oldver': oldver,
+ 'newver': newver})
+ try:
+ content = getURL(url % plugin)
+ m = VERSION.search(content)
+
+ if m and m.group(2) == version:
+ with open(fs_join("userplugins", type, filename), "wb") as f:
+ f.write(content)
+
+ updated.append((type, name))
+ else:
+ raise Exception, _("Version mismatch")
+
+ except Exception, e:
+ self.logError(_("Error updating plugin: %s") % filename, e)
+
+ if updated:
+ self.logInfo(_("*** Plugins updated ***"))
+
+ if self.core.pluginManager.reloadPlugins(updated):
+ exitcode = 1
+ else:
+ self.logWarning(_("pyLoad restart required to reload the updated plugins"))
+ self.info['plugins'] = True
+ exitcode = 2
+
+ self.manager.dispatchEvent("plugin_updated", updated)
+ else:
+ self.logInfo(_("No plugin updates available"))
+
+ # Exit codes:
+ # 0 = No plugin updated
+ # 1 = Plugins updated
+ # 2 = Plugins updated, but restart required
+ return exitcode
+
+
+ @Expose
+ def removePlugins(self, type_plugins):
+ """ delete plugins from disk """
+
+ if not type_plugins:
+ return
+
+ removed = set()
+
+ self.logDebug("Requested deletion of plugins: %s" % type_plugins)
+
+ for type, name in type_plugins:
+ rootplugins = os.path.join(pypath, "module", "plugins")
+
+ for dir in ("userplugins", rootplugins):
+ py_filename = fs_join(dir, type, name + ".py")
+ pyc_filename = py_filename + "c"
+
+ if type == "addon":
+ try:
+ self.manager.deactivateAddon(name)
+
+ except Exception, e:
+ self.logDebug(e)
+
+ for filename in (py_filename, pyc_filename):
+ if not exists(filename):
+ continue
+
+ try:
+ os.remove(filename)
+
+ except OSError, e:
+ self.logError(_("Error removing: %s") % filename, e)
+
+ else:
+ id = (type, name)
+ removed.add(id)
+
+ #: return a list of the plugins successfully removed
+ return list(removed)
diff --git a/pyload/plugin/addon/UserAgentSwitcher.py b/pyload/plugin/addon/UserAgentSwitcher.py
new file mode 100644
index 000000000..31a2b763b
--- /dev/null
+++ b/pyload/plugin/addon/UserAgentSwitcher.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import pycurl
+import random
+
+from pyload.plugin.Addon import Addon
+from pyload.utils import fs_encode
+
+
+class UserAgentSwitcher(Addon):
+ __name = "UserAgentSwitcher"
+ __type = "addon"
+ __version = "0.04"
+
+ __config = [("activated", "bool", "Activated" , True ),
+ ("uaf" , "file", "Random user-agents file" , "" ),
+ ("uar" , "bool", "Random user-agent" , False ),
+ ("uas" , "str" , "Custom user-agent string", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0")]
+
+ __description = """Custom user-agent"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def downloadPreparing(self, pyfile):
+ uar = self.getConfig('uar')
+ uaf = fs_encode(self.getConfig('uaf'))
+
+ if uar and os.path.isfile(uaf):
+ with open(uaf) as f:
+ uas = random.choice([ua for ua in f.read().splitlines()])
+ else:
+ uas = self.getConfig('uas')
+
+ if uas:
+ self.logDebug("Use custom user-agent string: " + uas)
+ pyfile.plugin.req.http.c.setopt(pycurl.USERAGENT, uas.encode('utf-8'))
diff --git a/pyload/plugin/addon/WindowsPhoneNotify.py b/pyload/plugin/addon/WindowsPhoneNotify.py
new file mode 100644
index 000000000..b1d1c8b0f
--- /dev/null
+++ b/pyload/plugin/addon/WindowsPhoneNotify.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+import httplib
+import time
+
+from pyload.plugin.Addon import Addon, Expose
+
+
+class WindowsPhoneNotify(Addon):
+ __name = "WindowsPhoneNotify"
+ __type = "addon"
+ __version = "0.09"
+
+ __config = [("id" , "str" , "Push ID" , "" ),
+ ("url" , "str" , "Push url" , "" ),
+ ("notifycaptcha" , "bool", "Notify captcha request" , True ),
+ ("notifypackage" , "bool", "Notify package finished" , True ),
+ ("notifyprocessed", "bool", "Notify packages processed" , True ),
+ ("notifyupdate" , "bool", "Notify plugin updates" , True ),
+ ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ),
+ ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ),
+ ("sendpermin" , "int" , "Max notifications per minute" , 12 ),
+ ("ignoreclient" , "bool", "Send notifications if client is connected", False)]
+
+ __description = """Send push notifications to Windows Phone"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt" , "phone-support@hotmail.de"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ event_list = ["allDownloadsProcessed", "plugin_updated"]
+
+
+ def setup(self):
+ self.last_notify = 0
+ self.notifications = 0
+
+
+ def plugin_updated(self, type_plugins):
+ if not self.getConfig('notifyupdate'):
+ return
+
+ self.notify(_("Plugins updated"), str(type_plugins))
+
+
+ def exit(self):
+ if not self.getConfig('notifyexit'):
+ return
+
+ if self.core.do_restart:
+ self.notify(_("Restarting pyLoad"))
+ else:
+ self.notify(_("Exiting pyLoad"))
+
+
+ def newCaptchaTask(self, task):
+ if not self.getConfig('notifycaptcha'):
+ return
+
+ self.notify(_("Captcha"), _("New request waiting user input"))
+
+
+ def packageFinished(self, pypack):
+ if self.getConfig('notifypackage'):
+ self.notify(_("Package finished"), pypack.name)
+
+
+ def allDownloadsProcessed(self):
+ if not self.getConfig('notifyprocessed'):
+ return
+
+ if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal):
+ self.notify(_("Package failed"), _("One or more packages was not completed successfully"))
+ else:
+ self.notify(_("All packages finished"))
+
+
+ def getXmlData(self, msg):
+ return ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> "
+ "<wp:Toast> <wp:Text1>pyLoad</wp:Text1> <wp:Text2>%s</wp:Text2> "
+ "</wp:Toast> </wp:Notification>" % msg)
+
+
+ @Expose
+ def notify(self,
+ event,
+ msg="",
+ key=(self.getConfig('id'), self.getConfig('url'))):
+
+ id, url = key
+
+ if not id or not url:
+ return
+
+ if self.core.isClientConnected() and not self.getConfig('ignoreclient'):
+ return
+
+ elapsed_time = time.time() - self.last_notify
+
+ if elapsed_time < self.getConf("sendtimewait"):
+ return
+
+ if elapsed_time > 60:
+ self.notifications = 0
+
+ elif self.notifications >= self.getConf("sendpermin"):
+ return
+
+
+ request = self.getXmlData("%s: %s" % (event, msg) if msg else event)
+ webservice = httplib.HTTP(url)
+
+ webservice.putrequest("POST", id)
+ 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.last_notify = time.time()
+ self.notifications += 1
diff --git a/pyload/plugin/addon/XMPPInterface.py b/pyload/plugin/addon/XMPPInterface.py
new file mode 100644
index 000000000..5ce5b5e8b
--- /dev/null
+++ b/pyload/plugin/addon/XMPPInterface.py
@@ -0,0 +1,251 @@
+# -*- 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.plugin.addon.IRCInterface import IRCInterface
+
+
+class XMPPInterface(IRCInterface, JabberClient):
+ __name = "XMPPInterface"
+ __type = "addon"
+ __version = "0.11"
+
+ __config = [("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"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "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 activate(self):
+ self.new_package = {}
+
+ self.start()
+
+
+ def packageFinished(self, pypack):
+ try:
+ if self.getConfig('info_pack'):
+ self.announce(_("Package finished: %s") % pypack.name)
+ except Exception:
+ 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 Exception:
+ pass
+
+
+ def run(self):
+ # connect to IRC etc.
+ self.connect()
+ try:
+ self.loop()
+ except Exception, ex:
+ self.logError(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("*** State changed: %s %r ***" % (state, arg))
+
+
+ def disconnected(self):
+ self.logDebug("Client was disconnected")
+
+
+ def stream_closed(self, stream):
+ self.logDebug("Stream was closed", stream)
+
+
+ def stream_error(self, err):
+ self.logDebug("Stream Error", 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("Message from %s received." % unicode(stanza.get_from()))
+ self.logDebug("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 Exception:
+ 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(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("Send message to", 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/plugin/addon/__init__.py b/pyload/plugin/addon/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/addon/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/captcha/AdYouLike.py b/pyload/plugin/captcha/AdYouLike.py
new file mode 100644
index 000000000..a29524bcc
--- /dev/null
+++ b/pyload/plugin/captcha/AdYouLike.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Captcha import Captcha
+from pyload.utils import json_loads
+
+
+class AdYouLike(Captcha):
+ __name = "AdYouLike"
+ __type = "captcha"
+ __version = "0.05"
+
+ __description = """AdYouLike captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ AYL_PATTERN = r'Adyoulike\.create\s*\((.+?)\)'
+ CALLBACK_PATTERN = r'(Adyoulike\.g\._jsonp_\d+)'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("AdYouLike html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.AYL_PATTERN, html)
+ n = re.search(self.CALLBACK_PATTERN, html)
+ if m and n:
+ self.key = (m.group(1).strip(), n.group(1).strip())
+ self.logDebug("Ayl|callback: %s | %s" % self.key)
+ return self.key #: key is the tuple(ayl, callback)
+ else:
+ self.logDebug("Ayl or callback not found")
+ return None
+
+
+ def challenge(self, key=None, html=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("AdYouLike key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ ayl, callback = key
+
+ # {"adyoulike":{"key":"P~zQ~O0zV0WTiAzC-iw0navWQpCLoYEP"},
+ # "all":{"element_id":"ayl_private_cap_92300","lang":"fr","env":"prod"}}
+ ayl = json_loads(ayl)
+
+ html = self.plugin.req.load("http://api-ayl.appspot.com/challenge",
+ get={'key': ayl['adyoulike']['key'],
+ 'env': ayl['all']['env'],
+ 'callback': callback})
+ try:
+ challenge = json_loads(re.search(callback + r'\s*\((.+?)\)', html).group(1))
+
+ except AttributeError:
+ errmsg = _("AdYouLike challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ return self.result(ayl, challenge), challenge
+
+
+ def result(self, server, 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"})
+
+ if isinstance(server, basestring):
+ server = json_loads(server)
+
+ if isinstance(challenge, basestring):
+ challenge = json_loads(challenge)
+
+ try:
+ instructions_visual = challenge['translations'][server['all']['lang']]['instructions_visual']
+ result = re.search(u'«(.+?)»', instructions_visual).group(1).strip()
+
+ except AttributeError:
+ errmsg = _("AdYouLike result not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ result = {'_ayl_captcha_engine': "adyoulike",
+ '_ayl_env': server['all']['env'],
+ '_ayl_tid': challenge['tid'],
+ '_ayl_token_challenge': challenge['token'],
+ '_ayl_response': response}
+
+ self.logDebug("Result: %s" % result)
+
+ return result
diff --git a/pyload/plugin/captcha/AdsCaptcha.py b/pyload/plugin/captcha/AdsCaptcha.py
new file mode 100644
index 000000000..c2caa7bf6
--- /dev/null
+++ b/pyload/plugin/captcha/AdsCaptcha.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from pyload.plugin.Captcha import Captcha
+
+
+class AdsCaptcha(Captcha):
+ __name = "AdsCaptcha"
+ __type = "captcha"
+ __version = "0.08"
+
+ __description = """AdsCaptcha captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ CAPTCHAID_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?CaptchaId=(\d+)'
+ PUBLICKEY_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?PublicKey=([\w-]+)'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("AdsCaptcha html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.PUBLICKEY_PATTERN, html)
+ n = re.search(self.CAPTCHAID_PATTERN, html)
+ if m and n:
+ self.key = (m.group(1).strip(), n.group(1).strip()) #: key is the tuple(PublicKey, CaptchaId)
+ self.logDebug("Key|id: %s | %s" % self.key)
+ return self.key
+ else:
+ self.logDebug("Key or id not found")
+ return None
+
+
+ def challenge(self, key=None, html=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("AdsCaptcha key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ PublicKey, CaptchaId = key
+
+ html = self.plugin.req.load("http://api.adscaptcha.com/Get.aspx",
+ get={'CaptchaId': CaptchaId,
+ 'PublicKey': PublicKey})
+ try:
+ challenge = re.search("challenge: '(.+?)',", html).group(1)
+ server = re.search("server: '(.+?)',", html).group(1)
+
+ except AttributeError:
+ errmsg = _("AdsCaptcha challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ return self.result(server, challenge), challenge
+
+
+ def result(self, server, challenge):
+ result = self.plugin.decryptCaptcha("%sChallenge.aspx" % server,
+ get={'cid': challenge, 'dummy': random.random()},
+ cookies=True,
+ imgtype="jpg")
+
+ self.logDebug("Result: %s" % result)
+
+ return result
diff --git a/pyload/plugin/captcha/ReCaptcha.py b/pyload/plugin/captcha/ReCaptcha.py
new file mode 100644
index 000000000..37da91a58
--- /dev/null
+++ b/pyload/plugin/captcha/ReCaptcha.py
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import random
+import re
+import time
+import urlparse
+
+from pyload.plugin.Captcha import Captcha
+
+
+class ReCaptcha(Captcha):
+ __name = "ReCaptcha"
+ __type = "captcha"
+ __version = "0.15"
+
+ __description = """ReCaptcha captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("zapp-brannigan", "fuerst.reinje@web.de")]
+
+ KEY_V2_PATTERN = r'(?:data-sitekey=["\']|["\']sitekey["\']:\s*["\'])([\w-]+)'
+ KEY_V1_PATTERN = r'(?:recaptcha(?:/api|\.net)/(?:challenge|noscript)\?k=|Recaptcha\.create\s*\(\s*["\'])([\w-]+)'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("ReCaptcha html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.KEY_V2_PATTERN, html) or re.search(self.KEY_V1_PATTERN, html)
+ if m:
+ self.key = m.group(1).strip()
+ self.logDebug("Key: %s" % self.key)
+ return self.key
+ else:
+ self.logDebug("Key not found")
+ return None
+
+
+ def challenge(self, key=None, html=None, version=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("ReCaptcha key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ if version in (1, 2):
+ return getattr(self, "_challenge_v%s" % version)(key)
+
+ elif not html and hasattr(self.plugin, "html") and self.plugin.html:
+ version = 2 if re.search(self.KEY_V2_PATTERN, self.plugin.html) else 1
+ return self.challenge(key, self.plugin.html, version)
+
+ else:
+ errmsg = _("ReCaptcha html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+
+ def _challenge_v1(self, key):
+ html = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge",
+ get={'k': key})
+ try:
+ challenge = re.search("challenge : '(.+?)',", html).group(1)
+ server = re.search("server : '(.+?)',", html).group(1)
+
+ except AttributeError:
+ errmsg = _("ReCaptcha challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ return self.result(server, challenge), challenge
+
+
+ def result(self, server, challenge):
+ result = self.plugin.decryptCaptcha("%simage" % server,
+ get={'c': challenge},
+ cookies=True,
+ forceUser=True,
+ imgtype="jpg")
+
+ self.logDebug("Result: %s" % result)
+
+ return result
+
+
+ def _collectApiInfo(self):
+ html = self.plugin.req.load("http://www.google.com/recaptcha/api.js")
+ a = re.search(r'po.src = \'(.*?)\';', html).group(1)
+ vers = a.split("/")[5]
+
+ self.logDebug("API version: %s" % vers)
+
+ language = a.split("__")[1].split(".")[0]
+
+ self.logDebug("API language: %s" % language)
+
+ html = self.plugin.req.load("https://apis.google.com/js/api.js")
+ b = re.search(r'"h":"(.*?)","', html).group(1)
+ jsh = b.decode('unicode-escape')
+
+ self.logDebug("API jsh-string: %s" % jsh)
+
+ return vers, language, jsh
+
+
+ def _prepareTimeAndRpc(self):
+ self.plugin.req.load("http://www.google.com/recaptcha/api2/demo")
+
+ millis = int(round(time.time() * 1000))
+
+ self.logDebug("Time: %s" % millis)
+
+ rand = random.randint(1, 99999999)
+ a = "0.%s" % str(rand * 2147483647)
+ rpc = int(100000000 * float(a))
+
+ self.logDebug("Rpc-token: %s" % rpc)
+
+ return millis, rpc
+
+
+ def _challenge_v2(self, key, parent=None):
+ if parent is None:
+ try:
+ parent = urlparse.urljoin("http://", urlparse.urlparse(self.plugin.pyfile.url).netloc)
+
+ except Exception:
+ parent = ""
+
+ botguardstring = "!A"
+ vers, language, jsh = self._collectApiInfo()
+ millis, rpc = self._prepareTimeAndRpc()
+
+ html = self.plugin.req.load("https://www.google.com/recaptcha/api2/anchor",
+ get={'k' : key,
+ 'hl' : language,
+ 'v' : vers,
+ 'usegapi' : "1",
+ 'jsh' : "%s#id=IO_%s" % (jsh, millis),
+ 'parent' : parent,
+ 'pfname' : "",
+ 'rpctoken': rpc})
+
+ token1 = re.search(r'id="recaptcha-token" value="(.*?)">', html)
+ self.logDebug("Token #1: %s" % token1.group(1))
+
+ html = self.plugin.req.load("https://www.google.com/recaptcha/api2/frame",
+ get={'c' : token1.group(1),
+ 'hl' : language,
+ 'v' : vers,
+ 'bg' : botguardstring,
+ 'k' : key,
+ 'usegapi': "1",
+ 'jsh' : jsh}).decode('unicode-escape')
+
+ token2 = re.search(r'"finput","(.*?)",', html)
+ self.logDebug("Token #2: %s" % token2.group(1))
+
+ token3 = re.search(r'"rresp","(.*?)",', html)
+ self.logDebug("Token #3: %s" % token3.group(1))
+
+ millis_captcha_loading = int(round(time.time() * 1000))
+ captcha_response = self.plugin.decryptCaptcha("https://www.google.com/recaptcha/api2/payload",
+ get={'c':token3.group(1), 'k':key},
+ cookies=True,
+ forceUser=True)
+ response = base64.b64encode('{"response":"%s"}' % captcha_response)
+
+ self.logDebug("Result: %s" % response)
+
+ timeToSolve = int(round(time.time() * 1000)) - millis_captcha_loading
+ timeToSolveMore = timeToSolve + int(float("0." + str(random.randint(1, 99999999))) * 500)
+
+ html = self.plugin.req.load("https://www.google.com/recaptcha/api2/userverify",
+ post={'k' : key,
+ 'c' : token3.group(1),
+ 'response': response,
+ 't' : timeToSolve,
+ 'ct' : timeToSolveMore,
+ 'bg' : botguardstring})
+
+ token4 = re.search(r'"uvresp","(.*?)",', html)
+ self.logDebug("Token #4: %s" % token4.group(1))
+
+ result = token4.group(1)
+
+ return result, None
diff --git a/pyload/plugin/captcha/SolveMedia.py b/pyload/plugin/captcha/SolveMedia.py
new file mode 100644
index 000000000..40a198b80
--- /dev/null
+++ b/pyload/plugin/captcha/SolveMedia.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Captcha import Captcha
+
+
+class SolveMedia(Captcha):
+ __name = "SolveMedia"
+ __type = "captcha"
+ __version = "0.12"
+
+ __description = """SolveMedia captcha service plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ KEY_PATTERN = r'api\.solvemedia\.com/papi/challenge\.(?:no)?script\?k=(.+?)["\']'
+
+
+ def detect_key(self, html=None):
+ if not html:
+ if hasattr(self.plugin, "html") and self.plugin.html:
+ html = self.plugin.html
+ else:
+ errmsg = _("SolveMedia html not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ m = re.search(self.KEY_PATTERN, html)
+ if m:
+ self.key = m.group(1).strip()
+ self.logDebug("Key: %s" % self.key)
+ return self.key
+ else:
+ self.logDebug("Key not found")
+ return None
+
+
+ def challenge(self, key=None, html=None):
+ if not key:
+ if self.detect_key(html):
+ key = self.key
+ else:
+ errmsg = _("SolveMedia key not found")
+ self.plugin.fail(errmsg)
+ raise TypeError(errmsg)
+
+ html = self.plugin.req.load("http://api.solvemedia.com/papi/challenge.noscript",
+ get={'k': key})
+ try:
+ challenge = re.search(r'<input type=hidden name="adcopy_challenge" id="adcopy_challenge" value="(.+?)">',
+ html).group(1)
+ server = "http://api.solvemedia.com/papi/media"
+
+ except AttributeError:
+ errmsg = _("SolveMedia challenge pattern not found")
+ self.plugin.fail(errmsg)
+ raise AttributeError(errmsg)
+
+ self.logDebug("Challenge: %s" % challenge)
+
+ result = self.result(server, challenge)
+
+ try:
+ magic = re.search(r'name="magic" value="(.+?)"', html).group(1)
+
+ except AttributeError:
+ self.logDebug("Magic code not found")
+
+ else:
+ if not self._verify(key, magic, result, challenge):
+ self.logDebug("Captcha code was invalid")
+
+ return result, challenge
+
+
+ def _verify(self, key, magic, result, challenge, ref=None): #@TODO: Clean up
+ if ref is None:
+ try:
+ ref = self.plugin.pyfile.url
+
+ except Exception:
+ ref = ""
+
+ html = self.plugin.req.load("http://api.solvemedia.com/papi/verify.noscript",
+ post={'adcopy_response' : result,
+ 'k' : key,
+ 'l' : "en",
+ 't' : "img",
+ 's' : "standard",
+ 'magic' : magic,
+ 'adcopy_challenge' : challenge,
+ 'ref' : ref})
+ try:
+ html = self.plugin.req.load(re.search(r'URL=(.+?)">', html).group(1))
+ gibberish = re.search(r'id=gibberish>(.+?)</textarea>', html).group(1)
+
+ except Exception:
+ return False
+
+ else:
+ return True
+
+
+ def result(self, server, challenge):
+ result = self.plugin.decryptCaptcha(server,
+ get={'c': challenge},
+ cookies=True,
+ imgtype="gif")
+
+ self.logDebug("Result: %s" % result)
+
+ return result
diff --git a/pyload/plugin/captcha/__init__.py b/pyload/plugin/captcha/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/captcha/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/container/CCF.py b/pyload/plugin/container/CCF.py
new file mode 100644
index 000000000..024987174
--- /dev/null
+++ b/pyload/plugin/container/CCF.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import MultipartPostHandler
+import re
+import urllib2
+
+from pyload.plugin.Container import Container
+from pyload.utils import fs_encode, fs_join
+
+
+class CCF(Container):
+ __name = "CCF"
+ __type = "container"
+ __version = "0.23"
+
+ __pattern = r'.+\.ccf$'
+
+ __description = """CCF container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Willnix", "Willnix@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def decrypt(self, pyfile):
+ fs_filename = fs_encode(pyfile.url.strip())
+ opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
+
+ dlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php',
+ {'src' : "ccf",
+ 'filename': "test.ccf",
+ 'upload' : open(fs_filename, "rb")}).read()
+
+ download_folder = self.config.get("general", "download_folder")
+ dlc_file = fs_join(download_folder, "tmp_%s.dlc" % pyfile.name)
+
+ try:
+ dlc = re.search(r'<dlc>(.+)</dlc>', dlc_content, re.S).group(1).decode('base64')
+
+ except AttributeError:
+ self.fail(_("Container is corrupted"))
+
+ with open(dlc_file, "w") as tempdlc:
+ tempdlc.write(dlc)
+
+ self.urls = [dlc_file]
diff --git a/pyload/plugin/container/DLC.py b/pyload/plugin/container/DLC.py
new file mode 100644
index 000000000..39b1597b0
--- /dev/null
+++ b/pyload/plugin/container/DLC.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+import xml.dom.minidom
+
+import Crypto
+
+from pyload.plugin.Container import Container
+from pyload.utils import decode, fs_encode
+
+
+class DLC(Container):
+ __name = "DLC"
+ __type = "container"
+ __version = "0.24"
+
+ __pattern = r'.+\.dlc$'
+
+ __description = """DLC container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("Schnusch", "Schnusch@users.noreply.github.com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ KEY = "cb99b5cbc24db398"
+ IV = "9bc24cb995cb8db3"
+ API_URL = "http://service.jdownloader.org/dlcrypt/service.php?srcType=dlc&destType=pylo&data=%s"
+
+
+ def decrypt(self, pyfile):
+ fs_filename = fs_encode(pyfile.url.strip())
+ with open(fs_filename) as dlc:
+ data = dlc.read().strip()
+
+ data += '=' * (-len(data) % 4)
+
+ dlc_key = data[-88:]
+ dlc_data = data[:-88].decode('base64')
+ dlc_content = self.load(self.API_URL % dlc_key)
+
+ try:
+ rc = re.search(r'<rc>(.+)</rc>', dlc_content, re.S).group(1).decode('base64')
+
+ except AttributeError:
+ self.fail(_("Container is corrupted"))
+
+ key = iv = Crypto.Cipher.AES.new(self.KEY, Crypto.Cipher.AES.MODE_CBC, self.IV).decrypt(rc)
+
+ self.data = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CBC, iv).decrypt(dlc_data).decode('base64')
+ self.packages = [(name or pyfile.name, links, name or pyfile.name) \
+ for name, links in self.getPackages()]
+
+
+ def getPackages(self):
+ root = xml.dom.minidom.parseString(self.data).documentElement
+ content = root.getElementsByTagName("content")[0]
+ return self.parsePackages(content)
+
+
+ def parsePackages(self, startNode):
+ return [(decode(node.getAttribute("name")).decode('base64'), self.parseLinks(node)) \
+ for node in startNode.getElementsByTagName("package")]
+
+
+ def parseLinks(self, startNode):
+ return [node.getElementsByTagName("url")[0].firstChild.data.decode('base64') \
+ for node in startNode.getElementsByTagName("file")]
diff --git a/pyload/plugin/container/RSDF.py b/pyload/plugin/container/RSDF.py
new file mode 100644
index 000000000..1de8ad8d4
--- /dev/null
+++ b/pyload/plugin/container/RSDF.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import binascii
+import re
+
+import Crypto
+
+from pyload.plugin.Container import Container
+from pyload.utils import fs_encode
+
+
+class RSDF(Container):
+ __name = "RSDF"
+ __type = "container"
+ __version = "0.29"
+
+ __pattern = r'.+\.rsdf$'
+
+ __description = """RSDF container decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("spoob", "spoob@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ KEY = "8C35192D964DC3182C6F84F3252239EB4A320D2500000000"
+ IV = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+
+
+ def decrypt(self, pyfile):
+ KEY = binascii.unhexlify(self.KEY)
+ IV = binascii.unhexlify(self.IV)
+
+ iv = Crypto.Cipher.AES.new(KEY, Crypto.Cipher.AES.MODE_ECB).encrypt(IV)
+ cipher = Crypto.Cipher.AES.new(KEY, Crypto.Cipher.AES.MODE_CFB, iv)
+
+ try:
+ fs_filename = fs_encode(pyfile.url.strip())
+ with open(fs_filename, 'r') as rsdf:
+ data = rsdf.read()
+
+ except IOError, e:
+ self.fail(e)
+
+ if re.search(r"<title>404 - Not Found</title>", data):
+ pyfile.setStatus("offline")
+
+ else:
+ try:
+ raw_links = binascii.unhexlify(''.join(data.split())).splitlines()
+
+ except TypeError:
+ self.fail(_("Container is corrupted"))
+
+ for link in raw_links:
+ if not link:
+ continue
+ link = cipher.decrypt(link.decode('base64')).replace('CCF: ', '')
+ self.urls.append(link)
diff --git a/pyload/plugin/container/TXT.py b/pyload/plugin/container/TXT.py
new file mode 100644
index 000000000..4ef29c0b7
--- /dev/null
+++ b/pyload/plugin/container/TXT.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+
+from pyload.plugin.Container import Container
+from pyload.utils import fs_encode
+
+
+class TXT(Container):
+ __name = "TXT"
+ __type = "container"
+ __version = "0.15"
+
+ __pattern = r'.+\.(txt|text)$'
+ __config = [("flush" , "bool" , "Flush list after adding", False ),
+ ("encoding", "string", "File encoding" , "utf-8")]
+
+ __description = """Read link lists in plain text formats"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("jeix", "jeix@hasnomail.com")]
+
+
+ def decrypt(self, pyfile):
+ try:
+ encoding = codecs.lookup(self.getConfig('encoding')).name
+
+ except Exception:
+ encoding = "utf-8"
+
+ fs_filename = fs_encode(pyfile.url.strip())
+ txt = codecs.open(fs_filename, 'r', encoding)
+ curPack = "Parsed links from %s" % pyfile.name
+ packages = {curPack:[],}
+
+ for link in txt.readlines():
+ 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
+ for key, value in packages.iteritems():
+ if not value:
+ packages.pop(key, None)
+
+ if self.getConfig('flush'):
+ try:
+ txt = open(fs_filename, 'wb')
+ txt.close()
+
+ except IOError:
+ self.logWarning(_("Failed to flush list"))
+
+ for name, links in packages.iteritems():
+ self.packages.append((name, links, name))
diff --git a/pyload/plugin/container/__init__.py b/pyload/plugin/container/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/container/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/crypter/BitshareCom.py b/pyload/plugin/crypter/BitshareCom.py
new file mode 100644
index 000000000..2a4d17da1
--- /dev/null
+++ b/pyload/plugin/crypter/BitshareCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class BitshareCom(SimpleCrypter):
+ __name = "BitshareCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?bitshare\.com/\?d=\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Bitshare.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a href="(http://bitshare\.com/files/.+)">.+</a></td>'
+ NAME_PATTERN = r'View public folder "(?P<N>.+)"</h1>'
diff --git a/pyload/plugin/crypter/C1NeonCom.py b/pyload/plugin/crypter/C1NeonCom.py
new file mode 100644
index 000000000..e0e1bda17
--- /dev/null
+++ b/pyload/plugin/crypter/C1NeonCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class C1NeonCom(DeadCrypter):
+ __name = "C1NeonCom"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?c1neon\.com/.+'
+ __config = []
+
+ __description = """C1neon.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
diff --git a/pyload/plugin/crypter/ChipDe.py b/pyload/plugin/crypter/ChipDe.py
new file mode 100644
index 000000000..164531ee5
--- /dev/null
+++ b/pyload/plugin/crypter/ChipDe.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class ChipDe(Crypter):
+ __name = "ChipDe"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?chip\.de/video/.+\.html'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Chip.de decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("4Christopher", "4Christopher@gmx.de")]
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url)
+ try:
+ f = re.search(r'"(http://video\.chip\.de/.+)"', self.html)
+ except Exception:
+ 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/plugin/crypter/CloudzillaTo.py b/pyload/plugin/crypter/CloudzillaTo.py
new file mode 100644
index 000000000..a12b2736c
--- /dev/null
+++ b/pyload/plugin/crypter/CloudzillaTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class CloudzillaTo(SimpleHoster):
+ __name = "CloudzillaTo"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?cloudzilla\.to/share/folder/(?P<ID>[\w^_]+)'
+
+ __description = """Cloudzilla.to folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'<span class="name" title="(?P<N>.+?)"'
+ OFFLINE_PATTERN = r'>File not found...<'
+
+ LINK_PATTERN = r'<a href="(.+?)" class="item_href">'
+
+ PASSWORD_PATTERN = r'<div id="pwd_protected">'
+
+
+ def checkErrors(self):
+ m = re.search(self.PASSWORD_PATTERN, self.html)
+ if m:
+ self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
+
+ if re.search(self.PASSWORD_PATTERN, self.html):
+ self.retry(reason="Wrong password")
diff --git a/pyload/plugin/crypter/CrockoCom.py b/pyload/plugin/crypter/CrockoCom.py
new file mode 100644
index 000000000..71dadd8d5
--- /dev/null
+++ b/pyload/plugin/crypter/CrockoCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class CrockoCom(SimpleCrypter):
+ __name = "CrockoCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?crocko\.com/f/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Crocko.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<td class="last"><a href="(.+?)">download</a>'
diff --git a/pyload/plugin/crypter/CryptItCom.py b/pyload/plugin/crypter/CryptItCom.py
new file mode 100644
index 000000000..615626f4f
--- /dev/null
+++ b/pyload/plugin/crypter/CryptItCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.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+'
+ __config = []
+
+ __description = """Crypt-it.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de")]
diff --git a/pyload/plugin/crypter/CzshareCom.py b/pyload/plugin/crypter/CzshareCom.py
new file mode 100644
index 000000000..0c106bddc
--- /dev/null
+++ b/pyload/plugin/crypter/CzshareCom.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class CzshareCom(Crypter):
+ __name = "CzshareCom"
+ __type = "crypter"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/folders/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Czshare.com folder decrypter plugin, now Sdilej.cz"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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.S)
+ if m is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/pyload/plugin/crypter/DailymotionComFolder.py b/pyload/plugin/crypter/DailymotionComFolder.py
new file mode 100644
index 000000000..06834f9a5
--- /dev/null
+++ b/pyload/plugin/crypter/DailymotionComFolder.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.utils import json_loads
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class DailymotionComFolder(Crypter):
+ __name = "DailymotionComFolder"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'https?://(?:www\.)?dailymotion\.com/((playlists/)?(?P<TYPE>playlist|user)/)?(?P<ID>[\w^_]+)(?(TYPE)|#)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Dailymotion.com channel & playlist decrypter"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def api_response(self, ref, req=None):
+ url = urlparse.urljoin("https://api.dailymotion.com/", ref)
+ html = self.load(url, get=req)
+ return json_loads(html)
+
+
+ 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 = fs_join(self.config.get("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/plugin/crypter/DataHu.py b/pyload/plugin/crypter/DataHu.py
new file mode 100644
index 000000000..a6c219d29
--- /dev/null
+++ b/pyload/plugin/crypter/DataHu.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DataHu(SimpleCrypter):
+ __name = "DataHu"
+ __type = "crypter"
+ __version = "0.06"
+
+ __pattern = r'http://(?:www\.)?data\.hu/dir/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Data.hu folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("crash", ""),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a href=\'(http://data\.hu/get/.+)\' target=\'_blank\'>\1</a>'
+ NAME_PATTERN = ur'<title>(?P<N>.+) Let\xf6lt\xe9se</title>'
+
+
+ def prepare(self):
+ super(DataHu, self).prepare()
+
+ if u'K\xe9rlek add meg a jelsz\xf3t' in self.html: #: Password protected
+ password = self.getPassword()
+ if not password:
+ self.fail(_("Password required"))
+
+ self.logDebug("The folder is password protected', 'Using password: " + password)
+
+ self.html = self.load(self.pyfile.url, post={'mappa_pass': password}, decode=True)
+
+ if u'Hib\xe1s jelsz\xf3' in self.html: #: Wrong password
+ self.fail(_("Wrong password"))
diff --git a/pyload/plugin/crypter/DdlstorageCom.py b/pyload/plugin/crypter/DdlstorageCom.py
new file mode 100644
index 000000000..e3e1f1956
--- /dev/null
+++ b/pyload/plugin/crypter/DdlstorageCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class DdlstorageCom(DeadCrypter):
+ __name = "DdlstorageCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?ddlstorage\.com/folder/\w+'
+ __config = []
+
+ __description = """DDLStorage.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/DepositfilesCom.py b/pyload/plugin/crypter/DepositfilesCom.py
new file mode 100644
index 000000000..d86f67426
--- /dev/null
+++ b/pyload/plugin/crypter/DepositfilesCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DepositfilesCom(SimpleCrypter):
+ __name = "DepositfilesCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?depositfiles\.com/folders/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Depositfiles.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<div class="progressName".*?>\s*<a href="(.+?)" title=".+?" target="_blank">'
diff --git a/pyload/plugin/crypter/Dereferer.py b/pyload/plugin/crypter/Dereferer.py
new file mode 100644
index 000000000..b4b52f278
--- /dev/null
+++ b/pyload/plugin/crypter/Dereferer.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter
+
+
+class Dereferer(SimpleDereferer):
+ __name = "Dereferer"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'https?://([^/]+)/.*?(?P<LINK>(ht|f)tps?(://|%3A%2F%2F).+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Crypter for dereferers"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/DevhostSt.py b/pyload/plugin/crypter/DevhostSt.py
new file mode 100644
index 000000000..7ede22106
--- /dev/null
+++ b/pyload/plugin/crypter/DevhostSt.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://d-h.st/users/shine/?fld_id=37263#files
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DevhostSt(SimpleCrypter):
+ __name = "DevhostSt"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?d-h\.st/users/(?P<USER>\w+)(/\?fld_id=(?P<ID>\d+))?'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """d-h.st folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'(?:/> |;">)<a href="(.+?)"(?!>Back to \w+<)'
+ OFFLINE_PATTERN = r'"/cHP">test\.png<'
+
+
+ def checkNameSize(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("File info (BEFORE): %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("File info (AFTER): %s" % self.info)
+
+ try:
+ if self.info['pattern']['ID'] == "0":
+ raise
+
+ p = r'href="(.+?)">Back to \w+<'
+ m = re.search(p, self.html)
+ html = self.load(urlparse.urljoin("http://d-h.st", m.group(1)),
+ cookies=False)
+
+ p = '\?fld_id=%s.*?">(.+?)<' % self.info['pattern']['ID']
+ m = re.search(p, html)
+ self.pyfile.name = m.group(1)
+
+ except Exception, e:
+ self.logDebug(e)
+ self.pyfile.name = self.info['pattern']['USER']
+
+ try:
+ folder = self.info['folder'] = self.pyfile.name
+
+ except Exception:
+ pass
+ self.logDebug("File name: %s" % self.pyfile.name,
+ "File folder: %s" % self.pyfile.name)
diff --git a/pyload/plugin/crypter/DlProtectCom.py b/pyload/plugin/crypter/DlProtectCom.py
new file mode 100644
index 000000000..757f7d592
--- /dev/null
+++ b/pyload/plugin/crypter/DlProtectCom.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import re
+import time
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class DlProtectCom(SimpleCrypter):
+ __name = "DlProtectCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?dl-protect\.com/((en|fr)/)?\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Dl-protect.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ COOKIES = [("dl-protect.com", "l", "en")]
+
+ 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"https?://(?:www\.)?dl-protect\.com/.+", self.req.http.lastEffectiveURL):
+ return [self.req.http.lastEffectiveURL]
+
+ post_req = {'key' : re.search(r'name="key" value="(.+?)"', self.html).group(1),
+ 'submitform': ""}
+
+ if "Please click on continue to see the content" in self.html:
+ post_req['submitform'] = "Continue"
+ self.wait(2)
+
+ else:
+ mstime = int(round(time.time() * 1000))
+ b64time = "_" + base64.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:]))
+
+ return re.findall(r'<a href="([^/].+?)" target="_blank">', self.html)
diff --git a/pyload/plugin/crypter/DontKnowMe.py b/pyload/plugin/crypter/DontKnowMe.py
new file mode 100644
index 000000000..e4edd8129
--- /dev/null
+++ b/pyload/plugin/crypter/DontKnowMe.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter
+
+
+class DontKnowMe(SimpleDereferer):
+ __name = "DontKnowMe"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?dontknow\.me/at/\?(?P<LINK>.+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """DontKnow.me decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("selaux", "")]
diff --git a/pyload/plugin/crypter/DuckCryptInfo.py b/pyload/plugin/crypter/DuckCryptInfo.py
new file mode 100644
index 000000000..64d6568ce
--- /dev/null
+++ b/pyload/plugin/crypter/DuckCryptInfo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+import BeautifulSoup
+
+from pyload.plugin.Crypter import Crypter
+
+
+class DuckCryptInfo(Crypter):
+ __name = "DuckCryptInfo"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?duckcrypt\.info/(folder|wait|link)/(\w+)/?(\w*)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """DuckCrypt.info decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
+
+
+ TIMER_PATTERN = r'<span id="timer">(.*)</span>'
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+
+ 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):
+ html = self.load("http://duckcrypt.info/ajax/auth.php?hash=" + str(m.group(2)))
+ m = re.match(self.__pattern, html)
+ self.logDebug("Redirectet to " + str(m.group(0)))
+ html = self.load(str(m.group(0)))
+ soup = BeautifulSoup.BeautifulSoup(html)
+ cryptlinks = soup.findAll("div", attrs={"class": "folderbox"})
+ self.logDebug("Redirectet to " + str(cryptlinks))
+ if not cryptlinks:
+ self.error(_("No link found"))
+ for clink in cryptlinks:
+ if clink.find("a"):
+ self.handleLink(clink.find("a")['href'])
+
+
+ def handleLink(self, url):
+ html = self.load(url)
+ soup = BeautifulSoup.BeautifulSoup(html)
+ self.urls = [soup.find("iframe")['src']]
+ if not self.urls:
+ self.logInfo(_("No link found"))
diff --git a/pyload/plugin/crypter/DuploadOrg.py b/pyload/plugin/crypter/DuploadOrg.py
new file mode 100644
index 000000000..445318ccc
--- /dev/null
+++ b/pyload/plugin/crypter/DuploadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class DuploadOrg(DeadCrypter):
+ __name = "DuploadOrg"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?dupload\.org/folder/\d+'
+ __config = []
+
+ __description = """Dupload.org folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/EasybytezCom.py b/pyload/plugin/crypter/EasybytezCom.py
new file mode 100644
index 000000000..06a958729
--- /dev/null
+++ b/pyload/plugin/crypter/EasybytezCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSCrypter import XFSCrypter
+
+
+class EasybytezCom(XFSCrypter):
+ __name = "EasybytezCom"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?easybytez\.com/users/\d+/\d+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Easybytez.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LOGIN_ACCOUNT = True
diff --git a/pyload/plugin/crypter/EmbeduploadCom.py b/pyload/plugin/crypter/EmbeduploadCom.py
new file mode 100644
index 000000000..942627ad9
--- /dev/null
+++ b/pyload/plugin/crypter/EmbeduploadCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.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 = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True ),
+ ("preferedHoster" , "str" , "Prefered hoster list (bar-separated)", "embedupload"),
+ ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
+
+ __description = """EmbedUpload.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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)
+
+ self.logDebug("PF: %s" % 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)
+
+ self.logDebug("IG: %s" % ignored_set)
+
+ tmp_links.extend(x[1] for x in m if x[0] not in ignored_set)
+ self.urls = self.getLocation(tmp_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/plugin/crypter/FilebeerInfo.py b/pyload/plugin/crypter/FilebeerInfo.py
new file mode 100644
index 000000000..5ec059375
--- /dev/null
+++ b/pyload/plugin/crypter/FilebeerInfo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class FilebeerInfo(DeadCrypter):
+ __name = "FilebeerInfo"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?filebeer\.info/\d*~f\w+'
+ __config = []
+
+ __description = """Filebeer.info folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/FilecloudIo.py b/pyload/plugin/crypter/FilecloudIo.py
new file mode 100644
index 000000000..91e7dfc3a
--- /dev/null
+++ b/pyload/plugin/crypter/FilecloudIo.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilecloudIo(SimpleCrypter):
+ __name = "FilecloudIo"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?(filecloud\.io|ifile\.it)/_\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filecloud.io folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'href="(http://filecloud\.io/\w+)" title'
+ NAME_PATTERN = r'>(?P<N>.+?) - filecloud\.io<'
diff --git a/pyload/plugin/crypter/FilecryptCc.py b/pyload/plugin/crypter/FilecryptCc.py
new file mode 100644
index 000000000..0507a580b
--- /dev/null
+++ b/pyload/plugin/crypter/FilecryptCc.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://filecrypt.cc/Container/64E039F859.html
+
+import binascii
+import re
+import urlparse
+
+import Crypto
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+class FilecryptCc(Crypter):
+ __name = "FilecryptCc"
+ __type = "crypter"
+ __version = "0.14"
+
+ __pattern = r'https?://(?:www\.)?filecrypt\.cc/Container/\w+'
+
+ __description = """Filecrypt.cc decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ # URL_REPLACEMENTS = [(r'.html$', ""), (r'$', ".html")] #@TODO: Extend SimpleCrypter
+
+ DLC_LINK_PATTERN = r'<button class="dlcdownload" type="button" title="Download \*.dlc" onclick="DownloadDLC\(\'(.+)\'\);"><i></i><span>dlc<'
+ WEBLINK_PATTERN = r"openLink.?'([\w_-]*)',"
+
+ CAPTCHA_PATTERN = r'<img id="nc" src="(.+?)"'
+ CIRCLE_CAPTCHA_PATTERN = r'<input type="image" src="(.+?)"'
+
+ MIRROR_PAGE_PATTERN = r'"[\w]*" href="(https?://(?:www\.)?filecrypt.cc/Container/\w+\.html\?mirror=\d+)">'
+
+
+ def setup(self):
+ self.links = []
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url)
+ self.base_url = self.pyfile.url.split("Container")[0]
+
+ if "content notfound" in self.html: #@NOTE: "content notfound" is NOT a typo
+ self.offline()
+
+ self.handlePasswordProtection()
+ self.handleCaptcha()
+ self.handleMirrorPages()
+
+ for handle in (self.handleCNL, self.handleWeblinks, self.handleDlcContainer):
+ handle()
+ if self.links:
+ self.packages = [(pyfile.package().name, self.links, pyfile.package().name)]
+ return
+
+
+ def handleMirrorPages(self):
+ if "mirror=" not in self.siteWithLinks:
+ return
+
+ mirror = re.findall(self.MIRROR_PAGE_PATTERN, self.siteWithLinks)
+
+ self.logInfo(_("Found %d mirrors") % len(mirror))
+
+ for i in mirror[1:]:
+ self.siteWithLinks = self.siteWithLinks + self.load(i).decode("utf-8", "replace")
+
+
+ def handlePasswordProtection(self):
+ if '<input type="text" name="password"' not in self.html:
+ return
+
+ self.logInfo(_("Folder is password protected"))
+
+ password = self.getPassword()
+
+ if not password:
+ self.fail(_("Please enter the password in package section and try again"))
+
+ self.html = self.load(self.pyfile.url, post={"password": password})
+
+
+ def handleCaptcha(self):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ m2 = re.search(self.CIRCLE_CAPTCHA_PATTERN, self.html)
+
+ if m: #: normal captcha
+ self.logDebug("Captcha-URL: %s" % m.group(1))
+
+ captcha_code = self.decryptCaptcha(urlparse.urljoin(self.base_url, m.group(1)),
+ forceUser=True,
+ imgtype="gif")
+
+ self.siteWithLinks = self.load(self.pyfile.url,
+ post={'recaptcha_response_field': captcha_code},
+ decode=True)
+ elif m2: #: circle captcha
+ self.logDebug("Captcha-URL: %s" % m2.group(1))
+
+ captcha_code = self.decryptCaptcha('%s%s?c=abc' %(self.base_url, m2.group(1)),
+ result_type='positional')
+
+ self.siteWithLinks = self.load(self.pyfile.url,
+ post={'button.x': captcha_code[0], 'button.y': captcha_code[1]},
+ decode=True)
+
+ else:
+ recaptcha = ReCaptcha(self)
+ captcha_key = recaptcha.detect_key()
+
+ if captcha_key:
+ response, challenge = recaptcha.challenge(captcha_key)
+ self.siteWithLinks = self.load(self.pyfile.url,
+ post={'g-recaptcha-response': response},
+ decode=True)
+ else:
+ self.logInfo(_("No captcha found"))
+ self.siteWithLinks = self.html
+
+ if "recaptcha_image" in self.siteWithLinks or "data-sitekey" in self.siteWithLinks:
+ self.invalidCaptcha()
+ self.retry()
+
+
+ def handleDlcContainer(self):
+ dlc = re.findall(self.DLC_LINK_PATTERN, self.siteWithLinks)
+
+ if not dlc:
+ return
+
+ for i in dlc:
+ self.links.append("%s/DLC/%s.dlc" % (self.base_url, i))
+
+
+ def handleWeblinks(self):
+ try:
+ weblinks = re.findall(self.WEBLINK_PATTERN, self.siteWithLinks)
+
+ for link in weblinks:
+ res = self.load("%s/Link/%s.html" % (self.base_url, link))
+ link2 = re.search('<iframe noresize src="(.*)"></iframe>', res)
+ res2 = self.load(link2.group(1), just_header=True)
+ self.links.append(res2['location'])
+
+ except Exception, e:
+ self.logDebug("Error decrypting weblinks: %s" % e)
+
+
+ def handleCNL(self):
+ try:
+ vjk = re.findall('<input type="hidden" name="jk" value="function f\(\){ return \'(.*)\';}">', self.siteWithLinks)
+ vcrypted = re.findall('<input type="hidden" name="crypted" value="(.*)">', self.siteWithLinks)
+
+ for i in xrange(len(vcrypted)):
+ self.links.extend(self._getLinks(vcrypted[i], vjk[i]))
+
+ except Exception, e:
+ self.logDebug("Error decrypting CNL: %s" % e)
+
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ key = binascii.unhexlify(str(jk))
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = Crypto.Cipher.AES.new(Key, Crypto.Cipher.AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ return links
diff --git a/pyload/plugin/crypter/FilefactoryCom.py b/pyload/plugin/crypter/FilefactoryCom.py
new file mode 100644
index 000000000..0b6290273
--- /dev/null
+++ b/pyload/plugin/crypter/FilefactoryCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilefactoryCom(SimpleCrypter):
+ __name = "FilefactoryCom"
+ __type = "crypter"
+ __version = "0.32"
+
+ __pattern = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filefactory.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
+
+ LINK_PATTERN = r'<td>\s*<a href="(.+?)"'
+ NAME_PATTERN = r'<h1>Files in <span>(?P<N>.+?)<'
+ PAGES_PATTERN = r'data-paginator-totalPages="(\d+)'
+
+
+ def loadPage(self, page_n):
+ return self.load(self.pyfile.url, get={'page': page_n, 'show': 100})
diff --git a/pyload/plugin/crypter/FilerNet.py b/pyload/plugin/crypter/FilerNet.py
new file mode 100644
index 000000000..a57f2908a
--- /dev/null
+++ b/pyload/plugin/crypter/FilerNet.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilerNet(SimpleCrypter):
+ __name = "FilerNet"
+ __type = "crypter"
+ __version = "0.42"
+
+ __pattern = r'https?://filer\.net/folder/\w{16}'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filer.net decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("nath_schwarz", "nathan.notwhite@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'href="(/get/\w{16})">(?!<)'
+
+ NAME_PATTERN = r'<h3>(?P<N>.+?) - <small'
+ OFFLINE_PATTERN = r'Nicht gefunden'
diff --git a/pyload/plugin/crypter/FileserveCom.py b/pyload/plugin/crypter/FileserveCom.py
new file mode 100644
index 000000000..bf593b545
--- /dev/null
+++ b/pyload/plugin/crypter/FileserveCom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class FileserveCom(Crypter):
+ __name = "FileserveCom"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?fileserve\.com/list/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """FileServe.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fionnc", "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.S)
+ if folder is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ 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)]
diff --git a/pyload/plugin/crypter/FilesonicCom.py b/pyload/plugin/crypter/FilesonicCom.py
new file mode 100644
index 000000000..5114d03b8
--- /dev/null
+++ b/pyload/plugin/crypter/FilesonicCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class FilesonicCom(DeadCrypter):
+ __name = "FilesonicCom"
+ __type = "crypter"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?filesonic\.com/folder/\w+'
+ __config = []
+
+ __description = """Filesonic.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/FilestubeCom.py b/pyload/plugin/crypter/FilestubeCom.py
new file mode 100644
index 000000000..fadeddedc
--- /dev/null
+++ b/pyload/plugin/crypter/FilestubeCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilestubeCom(SimpleCrypter):
+ __name = "FilestubeCom"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?filestube\.(?:com|to)/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filestube.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a class=\"file-link-main(?: noref)?\" [^>]* href=\"(http://[^\"]+)'
+ NAME_PATTERN = r'<h1\s*> (?P<N>.+) download\s*</h1>'
diff --git a/pyload/plugin/crypter/FiletramCom.py b/pyload/plugin/crypter/FiletramCom.py
new file mode 100644
index 000000000..4d6e898b4
--- /dev/null
+++ b/pyload/plugin/crypter/FiletramCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FiletramCom(SimpleCrypter):
+ __name = "FiletramCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?filetram\.com/[^/]+/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Filetram.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'\s+(http://.+)'
+ NAME_PATTERN = r'<title>(?P<N>.+?) - Free Download'
diff --git a/pyload/plugin/crypter/FiredriveCom.py b/pyload/plugin/crypter/FiredriveCom.py
new file mode 100644
index 000000000..be3a9ebdb
--- /dev/null
+++ b/pyload/plugin/crypter/FiredriveCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class FiredriveCom(DeadCrypter):
+ __name = "FiredriveCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?(firedrive|putlocker)\.com/share/.+'
+ __config = []
+
+ __description = """Firedrive.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/crypter/FourChanOrg.py b/pyload/plugin/crypter/FourChanOrg.py
new file mode 100644
index 000000000..6bafc6eb4
--- /dev/null
+++ b/pyload/plugin/crypter/FourChanOrg.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# Based on 4chandl by Roland Beermann (https://gist.github.com/enkore/3492599)
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class FourChanOrg(Crypter):
+ __name = "FourChanOrg"
+ __type = "crypter"
+ __version = "0.31"
+
+ __pattern = r'http://(?:www\.)?boards\.4chan\.org/\w+/res/(\d+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """4chan.org folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+
+ 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/plugin/crypter/FreakhareCom.py b/pyload/plugin/crypter/FreakhareCom.py
new file mode 100644
index 000000000..a0c19ac04
--- /dev/null
+++ b/pyload/plugin/crypter/FreakhareCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreakhareCom(SimpleCrypter):
+ __name = "FreakhareCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?freakshare\.com/folder/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Freakhare.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<a href="(http://freakshare\.com/files/.+?)" target="_blank">'
+ NAME_PATTERN = r'Folder:</b> (?P<N>.+)'
+ PAGES_PATTERN = r'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/plugin/crypter/FreetexthostCom.py b/pyload/plugin/crypter/FreetexthostCom.py
new file mode 100644
index 000000000..1f82c8ffa
--- /dev/null
+++ b/pyload/plugin/crypter/FreetexthostCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreetexthostCom(SimpleCrypter):
+ __name = "FreetexthostCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?freetexthost\.com/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Freetexthost.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getLinks(self):
+ m = re.search(r'<div id="contentsinner">\s*(.+)<div class="viewcount">', self.html, re.S)
+ if m is None:
+ self.error(_("Unable to extract links"))
+ links = m.group(1)
+ return links.strip().split("<br />\r\n")
diff --git a/pyload/plugin/crypter/FshareVn.py b/pyload/plugin/crypter/FshareVn.py
new file mode 100644
index 000000000..09adfee3e
--- /dev/null
+++ b/pyload/plugin/crypter/FshareVn.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class FshareVn(SimpleCrypter):
+ __name = "FshareVn"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?fshare\.vn/folder/.+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Fshare.vn folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<li class="w_80pc"><a href="(.+?)" target="_blank">'
diff --git a/pyload/plugin/crypter/Go4UpCom.py b/pyload/plugin/crypter/Go4UpCom.py
new file mode 100644
index 000000000..eef9efddc
--- /dev/null
+++ b/pyload/plugin/crypter/Go4UpCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class Go4UpCom(SimpleCrypter):
+ __name = "Go4UpCom"
+ __type = "crypter"
+ __version = "0.12"
+
+ __pattern = r'http://go4up\.com/(dl/\w{12}|rd/\w{12}/\d+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Go4Up.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("rlindner81", "rlindner81@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'(http://go4up\.com/rd/.+?)<'
+
+ NAME_PATTERN = r'<title>Download (.+?)<'
+
+ OFFLINE_PATTERN = r'>\s*(404 Page Not Found|File not Found|Mirror does not exist)'
+
+
+ def getLinks(self):
+ links = []
+
+ m = re.search(r'(/download/gethosts/.+?)"', self.html)
+ if m:
+ self.html = self.load(urlparse.urljoin("http://go4up.com/", m.group(1)))
+ pages = [self.load(url) for url in re.findall(self.LINK_PATTERN, self.html)]
+ else:
+ pages = [self.html]
+
+ for html in pages:
+ try:
+ links.append(re.search(r'<b><a href="(.+?)"', html).group(1))
+ except Exception:
+ continue
+
+ return links
diff --git a/pyload/plugin/crypter/GooGl.py b/pyload/plugin/crypter/GooGl.py
new file mode 100644
index 000000000..552a9ea01
--- /dev/null
+++ b/pyload/plugin/crypter/GooGl.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.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+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Goo.gl decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "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/plugin/crypter/HoerbuchIn.py b/pyload/plugin/crypter/HoerbuchIn.py
new file mode 100644
index 000000000..455d0abdf
--- /dev/null
+++ b/pyload/plugin/crypter/HoerbuchIn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+import BeautifulSoup, BeautifulStoneSoup
+
+from pyload.plugin.Crypter import Crypter
+
+
+class HoerbuchIn(Crypter):
+ __name = "HoerbuchIn"
+ __type = "crypter"
+ __version = "0.60"
+
+ __pattern = r'http://(?:www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out\.php\?.+|protection/folder_\d+\.html)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Hoerbuch.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("mkaay", "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):
+ html = self.load(pyfile.url)
+ soup = BeautifulSoup.BeautifulSoup(html, 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
+ html = self.load(url, post={"viewed": "adpg"})
+
+ links = []
+ pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"")
+ for hoster, lid in pattern.findall(html):
+ 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/plugin/crypter/HotfileCom.py b/pyload/plugin/crypter/HotfileCom.py
new file mode 100644
index 000000000..8fc10cf88
--- /dev/null
+++ b/pyload/plugin/crypter/HotfileCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class HotfileCom(DeadCrypter):
+ __name = "HotfileCom"
+ __type = "crypter"
+ __version = "0.30"
+
+ __pattern = r'https?://(?:www\.)?hotfile\.com/list/\w+/\w+'
+ __config = []
+
+ __description = """Hotfile.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
diff --git a/pyload/plugin/crypter/ILoadTo.py b/pyload/plugin/crypter/ILoadTo.py
new file mode 100644
index 000000000..e00a88743
--- /dev/null
+++ b/pyload/plugin/crypter/ILoadTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class ILoadTo(DeadCrypter):
+ __name = "ILoadTo"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?iload\.to/go/\d+-[\w.-]+/'
+ __config = []
+
+ __description = """Iload.to decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("hzpz", "")]
diff --git a/pyload/plugin/crypter/ImgurComAlbum.py b/pyload/plugin/crypter/ImgurComAlbum.py
new file mode 100644
index 000000000..33a5553f9
--- /dev/null
+++ b/pyload/plugin/crypter/ImgurComAlbum.py
@@ -0,0 +1,28 @@
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+from pyload.utils import uniqify
+
+
+class ImgurComAlbum(SimpleCrypter):
+ __name = "ImgurComAlbum"
+ __type = "crypter"
+ __version = "0.51"
+
+ __pattern = r'https?://(?:www\.|m\.)?imgur\.com/(a|gallery|)/?\w{5,7}'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Imgur.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("nath_schwarz", "nathan.notwhite@gmail.com")]
+
+
+ NAME_PATTERN = r'(?P<N>.+?) - 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/plugin/crypter/LetitbitNet.py b/pyload/plugin/crypter/LetitbitNet.py
new file mode 100644
index 000000000..51f54014f
--- /dev/null
+++ b/pyload/plugin/crypter/LetitbitNet.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class LetitbitNet(Crypter):
+ __name = "LetitbitNet"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?letitbit\.net/folder/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Letitbit.net folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("DHMH", "webmaster@pcProfil.de"),
+ ("z00nx", "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.S)
+ if folder is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ self.urls.extend(re.findall(self.LINK_PATTERN, folder.group(0)))
diff --git a/pyload/plugin/crypter/LinkCryptWs.py b/pyload/plugin/crypter/LinkCryptWs.py
new file mode 100644
index 000000000..98a796aed
--- /dev/null
+++ b/pyload/plugin/crypter/LinkCryptWs.py
@@ -0,0 +1,322 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import re
+
+import pycurl
+
+import Crypto
+
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import html_unescape
+
+
+class LinkCryptWs(Crypter):
+ __name = "LinkCryptWs"
+ __type = "crypter"
+ __version = "0.08"
+
+ __pattern = r'http://(?:www\.)?linkcrypt\.ws/(dir|container)/(?P<ID>\w+)'
+
+ __description = """LinkCrypt.ws decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("kagenoshin", "kagenoshin[AT]gmx[DOT]ch"),
+ ("glukgluk", ""),
+ ("Gummibaer", "")]
+
+
+ CRYPTED_KEY = "crypted"
+ JK_KEY = "jk"
+
+
+ def setup(self):
+ self.captcha = False
+ self.links = []
+ self.sources = ['cnl', 'web', 'dlc', 'rsdf', 'ccf']
+
+
+ def prepare(self):
+ # Init
+ self.fileid = re.match(self.__pattern, self.pyfile.url).group('ID')
+
+ self.req.cj.setCookie("linkcrypt.ws", "language", "en")
+
+ # Request package
+ self.req.http.c.setopt(pycurl.USERAGENT, "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko") #: better chance to not get those key-captchas
+ self.html = self.load(self.pyfile.url)
+
+
+ def decrypt(self, pyfile):
+ if not self.js:
+ self.fail(_("Missing JS Engine"))
+
+ self.prepare()
+
+ if not self.isOnline():
+ self.offline()
+
+ if self.isKeyCaptchaProtected():
+ self.retry(8, 15, _("Can't handle Key-Captcha"))
+
+ if self.isCaptchaProtected():
+ self.captcha = True
+ self.unlockCaptchaProtection()
+ self.handleCaptchaErrors()
+
+ # Check for protection
+ if self.isPasswordProtected():
+ self.unlockPasswordProtection()
+ self.handleErrors()
+
+ # get unrar password
+ self.getunrarpw()
+
+ # Get package name and folder
+ package_name, folder_name = self.getPackageInfo()
+
+ # get the container definitions from script section
+ self.get_container_html()
+
+ # Extract package links
+ for type in self.sources:
+ links = self.handleLinkSource(type)
+
+ if links:
+ self.links.extend(links)
+ break
+
+ if self.links:
+ self.packages = [(package_name, self.links, folder_name)]
+
+
+ def isOnline(self):
+ if "<title>Linkcrypt.ws // Error 404</title>" in self.html:
+ self.logDebug("folder doesen't exist anymore")
+ return False
+ else:
+ return True
+
+
+ def isPasswordProtected(self):
+ if "Authorizing" in self.html:
+ self.logDebug("Links are password protected")
+ return True
+ else:
+ return False
+
+
+ def isCaptchaProtected(self):
+ if 'id="captcha">' in self.html:
+ self.logDebug("Links are captcha protected")
+ return True
+ else:
+ return False
+
+
+ def isKeyCaptchaProtected(self):
+ if re.search(r'>If the folder does not open after klick on <', self.html, re.I):
+ return True
+ else:
+ return False
+
+
+ def unlockPasswordProtection(self):
+ password = self.getPassword()
+
+ if password:
+ self.logDebug("Submitting password [%s] for protected links" % password)
+ self.html = self.load(self.pyfile.url, post={"password": password, 'x': "0", 'y': "0"})
+ else:
+ self.fail(_("Folder is password protected"))
+
+
+ def unlockCaptchaProtection(self):
+ captcha_url = re.search(r'<form.*?id\s*?=\s*?"captcha"[^>]*?>.*?<\s*?input.*?src="(.+?)"', self.html, re.I | re.S).group(1)
+ captcha_code = self.decryptCaptcha(captcha_url, forceUser=True, imgtype="gif", result_type='positional')
+
+ self.html = self.load(self.pyfile.url, post={"x": captcha_code[0], "y": captcha_code[1]})
+
+
+ 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 getunrarpw(self):
+ sitein = self.html
+ indexi = sitein.find("|source|") + 8
+ indexe = sitein.find("|",indexi)
+
+ unrarpw = sitein[indexi:indexe]
+
+ if not (unrarpw == "Password" or "Dateipasswort") :
+ self.logDebug("File password set to: [%s]"% unrarpw)
+ self.pyfile.package().password = unrarpw
+
+
+ def handleErrors(self):
+ if self.isPasswordProtected():
+ self.fail(_("Incorrect password"))
+
+
+ def handleCaptchaErrors(self):
+ if self.captcha:
+ if "Your choice was wrong!" in self.html:
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+
+ def handleLinkSource(self, type):
+ if type == 'cnl':
+ return self.handleCNL2()
+
+ elif type == 'web':
+ return self.handleWebLinks()
+
+ elif type in ('rsdf', 'ccf', 'dlc'):
+ return self.handleContainer(type)
+
+ else:
+ self.fail(_("Unknown source type: %s") % type) #@TODO: Replace with self.error in 0.4.10
+
+
+ def handleWebLinks(self):
+ self.logDebug("Search for Web links ")
+
+ package_links = []
+ pattern = r'<form action="http://linkcrypt.ws/out.html"[^>]*?>.*?<input[^>]*?value="(.+?)"[^>]*?name="file"'
+ ids = re.findall(pattern, self.html, re.I | re.S)
+
+ self.logDebug("Decrypting %d Web links" % len(ids))
+
+ for idx, weblink_id in enumerate(ids):
+ try:
+ self.logDebug("Decrypting Web link %d, %s" % (idx + 1, weblink_id))
+
+ res = self.load("http://linkcrypt.ws/out.html", post = {'file':weblink_id})
+
+ indexs = res.find("window.location =") + 19
+ indexe = res.find('"', indexs)
+
+ link2 = res[indexs:indexe]
+
+ self.logDebug(link2)
+
+ link2 = html_unescape(link2)
+ package_links.append(link2)
+
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link %s, %s" % (weblink_id, detail))
+
+ return package_links
+
+
+ def get_container_html(self):
+ self.container_html = []
+
+ script = re.search(r'<div.*?id="ad_cont".*?<script.*?javascrip[^>]*?>(.*?)</script', self.html, re.I | re.S)
+
+ if script:
+ container_html_text = script.group(1)
+ container_html_text.strip()
+ self.container_html = container_html_text.splitlines()
+
+
+ def handle_javascript(self, line):
+ return self.js.eval(line.replace('{}))',"{}).replace('document.open();document.write','').replace(';document.close();',''))"))
+
+
+ def handleContainer(self, type):
+ package_links = []
+ type = type.lower()
+
+ self.logDebug('Search 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") % type) #@TODO: Replace with self.error in 0.4.10
+
+ for line in self.container_html:
+ if type in line:
+ jseval = self.handle_javascript(line)
+ clink = re.search(r'href=["\'](["\']+)', jseval, re.I)
+
+ if not clink:
+ continue
+
+ self.logDebug("clink avaible")
+
+ package_name, folder_name = self.getPackageInfo()
+ self.logDebug("Added package with name %s.%s and container link %s" %( package_name, type, clink.group(1)))
+ self.core.api.uploadContainer( "%s.%s" %(package_name, type), self.load(clink.group(1)))
+ return "Found it"
+
+ return package_links
+
+
+ def handleCNL2(self):
+ self.logDebug("Search for CNL links")
+
+ package_links = []
+ cnl_line = None
+
+ for line in self.container_html:
+ if "cnl" in line:
+ cnl_line = line
+ break
+
+ if cnl_line:
+ self.logDebug("cnl_line gefunden")
+
+ try:
+ cnl_section = self.handle_javascript(cnl_line)
+ (vcrypted, vjk) = self._getCipherParams(cnl_section)
+ for (crypted, jk) in zip(vcrypted, vjk):
+ package_links.extend(self._getLinks(crypted, jk))
+ except Exception:
+ self.logError(_("Unable to decrypt CNL links (JS Error) try to get over links"))
+ return self.handleWebLinks()
+
+ return package_links
+
+
+ def _getCipherParams(self, cnl_section):
+ # Get jk
+ jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.JK_KEY
+ vjk = re.findall(jk_re, cnl_section)
+
+ # Get crypted
+ crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.CRYPTED_KEY
+ vcrypted = re.findall(crypted_re, cnl_section)
+
+ # 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)
+ key = binascii.unhexlify(jreturn)
+
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = Crypto.Cipher.AES.new(Key, Crypto.Cipher.AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+
+ return links
diff --git a/pyload/plugin/crypter/LinkSaveIn.py b/pyload/plugin/crypter/LinkSaveIn.py
new file mode 100644
index 000000000..8c6f0b001
--- /dev/null
+++ b/pyload/plugin/crypter/LinkSaveIn.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleDereferer import SimpleDereferer
+
+
+class LinkSaveIn(SimpleDereferer):
+ __name = "LinkSaveIn"
+ __type = "crypter"
+ __version = "2.03"
+
+ __pattern = r'https?://(?:www\.)?linksave\.in/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """LinkSave.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ COOKIES = [("linksave.in", "Linksave_Language", "english")]
+
+ OFFLINE_PATTERN = r'>(Error )?404 -'
diff --git a/pyload/plugin/crypter/LinkdecrypterCom.py b/pyload/plugin/crypter/LinkdecrypterCom.py
new file mode 100644
index 000000000..907e3accd
--- /dev/null
+++ b/pyload/plugin/crypter/LinkdecrypterCom.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class LinkdecrypterCom(Crypter):
+ __name = "LinkdecrypterCom"
+ __type = "crypter"
+ __version = "0.29"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Linkdecrypter.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("flowlee", "")]
+
+
+ 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 setup(self):
+ self.password = self.getPassword()
+ self.req.setOption("timeout", 300)
+
+
+ def decrypt(self, pyfile):
+ retries = 5
+
+ post_dict = {"link_cache": "on", "pro_links": pyfile.url, "modo_links": "text"}
+ self.html = self.load('http://linkdecrypter.com/', post=post_dict, decode=True)
+
+ while retries:
+ m = re.search(self.TEXTAREA_PATTERN, self.html, re.S)
+ if m:
+ self.urls = [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.password:
+ self.logInfo(_("Password protected link"))
+ self.html = self.load('http://linkdecrypter.com/', post={'password': self.password}, decode=True)
+ else:
+ self.fail(_("Missing password"))
+
+ else:
+ retries -= 1
+ self.html = self.load('http://linkdecrypter.com/', decode=True)
diff --git a/pyload/plugin/crypter/LixIn.py b/pyload/plugin/crypter/LixIn.py
new file mode 100644
index 000000000..d15761014
--- /dev/null
+++ b/pyload/plugin/crypter/LixIn.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class LixIn(Crypter):
+ __name = "LixIn"
+ __type = "crypter"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?lix\.in/(?P<ID>.+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Lix.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
+
+
+ CAPTCHA_PATTERN = r'<img src="(captcha_img\.php\?.*?)"'
+ SUBMIT_PATTERN = r'value=\'continue.*?\''
+ LINK_PATTERN = r'name="ifram" src="(.*?)"'
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+
+ m = re.match(self.__pattern, url)
+ if m is None:
+ self.error(_("Unable to identify file ID"))
+
+ id = m.group('ID')
+ self.logDebug("File id is %s" % id)
+
+ self.html = self.load(url, decode=True)
+
+ m = re.search(self.SUBMIT_PATTERN, self.html)
+ if m is None:
+ self.error(_("Link doesn't seem valid"))
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ for _i 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(1))
+ self.html = self.load(url, decode=True,
+ post={"capt": captcharesult, "submit": "submit", "tiny": id})
+ else:
+ self.logDebug("No captcha/captcha solved")
+ else:
+ self.html = self.load(url, decode=True, post={"submit": "submit", "tiny": id})
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("Unable to find destination url"))
+ else:
+ self.urls = [m.group(1)]
+ self.logDebug("Found link %s, adding to package" % self.urls[0])
diff --git a/pyload/plugin/crypter/LofCc.py b/pyload/plugin/crypter/LofCc.py
new file mode 100644
index 000000000..680027b43
--- /dev/null
+++ b/pyload/plugin/crypter/LofCc.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class LofCc(DeadCrypter):
+ __name = "LofCc"
+ __type = "crypter"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?lof\.cc/(.+)'
+ __config = []
+
+ __description = """Lof.cc decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/crypter/MBLinkInfo.py b/pyload/plugin/crypter/MBLinkInfo.py
new file mode 100644
index 000000000..7b39f9b5b
--- /dev/null
+++ b/pyload/plugin/crypter/MBLinkInfo.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class MBLinkInfo(DeadCrypter):
+ __name = "MBLinkInfo"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?mblink\.info/?\?id=(\d+)'
+ __config = []
+
+ __description = """MBLink.info decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Gummibaer", "Gummibaer@wiki-bierkiste.de"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/MediafireCom.py b/pyload/plugin/crypter/MediafireCom.py
new file mode 100644
index 000000000..aae727a90
--- /dev/null
+++ b/pyload/plugin/crypter/MediafireCom.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.hoster.MediafireCom import checkHTMLHeader
+from pyload.utils import json_loads
+
+
+class MediafireCom(Crypter):
+ __name = "MediafireCom"
+ __type = "crypter"
+ __version = "0.14"
+
+ __pattern = r'http://(?:www\.)?mediafire\.com/(folder/|\?sharekey=|\?\w{13}($|[/#]))'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Mediafire.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ FOLDER_KEY_PATTERN = r'var afI= \'(\w+)'
+ LINK_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.LINK_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",
+ get={'folder_key' : folder_key,
+ 'response_format': "json",
+ 'version' : 1}))
+ # 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)
diff --git a/pyload/plugin/crypter/MegaCoNz.py b/pyload/plugin/crypter/MegaCoNz.py
new file mode 100644
index 000000000..c66b3f112
--- /dev/null
+++ b/pyload/plugin/crypter/MegaCoNz.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class MegaCoNz(Crypter):
+ __name = "MegaCoNz"
+ __type = "crypter"
+ __version = "0.04"
+
+ __pattern = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#F!(?P<ID>[\w^_]+)!(?P<KEY>[\w,\\-]+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Mega.co.nz folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def setup(self):
+ self.req.setOption("timeout", 300)
+
+
+ def decrypt(self, pyfile):
+ url = "https://mega.co.nz/#F!%s!%s" % re.match(self.__pattern, pyfile.url).groups()
+ self.html = self.load("http://rapidgen.org/linkfinder", post={'linklisturl': url})
+ self.urls = re.findall(r'(https://mega.co.nz/#N!.+?)<', self.html)
diff --git a/pyload/plugin/crypter/MegaRapidCz.py b/pyload/plugin/crypter/MegaRapidCz.py
new file mode 100644
index 000000000..fe490464a
--- /dev/null
+++ b/pyload/plugin/crypter/MegaRapidCz.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class MegaRapidCz(SimpleCrypter):
+ __name = "MegaRapidCz"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?(share|mega)rapid\.cz/slozka/\d+/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Share-Rapid.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<td class="soubor".*?><a href="(.+?)">'
diff --git a/pyload/plugin/crypter/MegauploadCom.py b/pyload/plugin/crypter/MegauploadCom.py
new file mode 100644
index 000000000..f300408e8
--- /dev/null
+++ b/pyload/plugin/crypter/MegauploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class MegauploadCom(DeadCrypter):
+ __name = "MegauploadCom"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?megaupload\.com/(\?f|xml/folderfiles\.php\?.*&?folderid)=\w+'
+ __config = []
+
+ __description = """Megaupload.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/Movie2KTo.py b/pyload/plugin/crypter/Movie2KTo.py
new file mode 100644
index 000000000..e807855af
--- /dev/null
+++ b/pyload/plugin/crypter/Movie2KTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class Movie2KTo(DeadCrypter):
+ __name = "Movie2KTo"
+ __type = "crypter"
+ __version = "0.51"
+
+ __pattern = r'http://(?:www\.)?movie2k\.to/(.+)\.html'
+ __config = []
+
+ __description = """Movie2k.to decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("4Christopher", "4Christopher@gmx.de")]
diff --git a/pyload/plugin/crypter/MultiUpOrg.py b/pyload/plugin/crypter/MultiUpOrg.py
new file mode 100644
index 000000000..54d9a979d
--- /dev/null
+++ b/pyload/plugin/crypter/MultiUpOrg.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class MultiUpOrg(SimpleCrypter):
+ __name = "MultiUpOrg"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?multiup\.org/(en|fr)/(?P<TYPE>project|download|miror)/\w+(/\w+)?'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """MultiUp.org decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<title>.*(?:Project|Projet|ownload|élécharger) (?P<N>.+?) (\(|- )'
+
+
+ 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 = urlparse.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/plugin/crypter/MultiloadCz.py b/pyload/plugin/crypter/MultiloadCz.py
new file mode 100644
index 000000000..2e2d69037
--- /dev/null
+++ b/pyload/plugin/crypter/MultiloadCz.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class MultiloadCz(Crypter):
+ __name = "MultiloadCz"
+ __type = "crypter"
+ __version = "0.40"
+
+ __pattern = r'http://(?:[^/]*\.)?multiload\.cz/(stahnout|slozka)/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True),
+ ("usedHoster" , "str" , "Prefered hoster list (bar-separated)", "" ),
+ ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )]
+
+ __description = """Multiload.cz decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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)
diff --git a/pyload/plugin/crypter/MultiuploadCom.py b/pyload/plugin/crypter/MultiuploadCom.py
new file mode 100644
index 000000000..439e0e69b
--- /dev/null
+++ b/pyload/plugin/crypter/MultiuploadCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class MultiuploadCom(DeadCrypter):
+ __name = "MultiuploadCom"
+ __type = "crypter"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?multiupload\.(com|nl)/\w+'
+
+ __description = """MultiUpload.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/NCryptIn.py b/pyload/plugin/crypter/NCryptIn.py
new file mode 100644
index 000000000..2b357395b
--- /dev/null
+++ b/pyload/plugin/crypter/NCryptIn.py
@@ -0,0 +1,310 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import re
+
+import Crypto
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+class NCryptIn(Crypter):
+ __name = "NCryptIn"
+ __type = "crypter"
+ __version = "1.34"
+
+ __pattern = r'http://(?:www\.)?ncrypt\.in/(?P<TYPE>folder|link|frame)-([^/\?]+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """NCrypt.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("stickell", "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.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 package_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.S)
+ 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.S)
+ if form:
+ 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.S).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)
+ response, challenge = recaptcha.challenge(captcha_key)
+ postData['recaptcha_challenge_field'] = challenge
+ postData['recaptcha_response_field'] = response
+
+ # 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.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.error(_('Unknown source type "%s"') % 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 Exception:
+ 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)/(\w+)'
+ 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)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = Crypto.Cipher.AES.new(Key, Crypto.Cipher.AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugin/crypter/NetfolderIn.py b/pyload/plugin/crypter/NetfolderIn.py
new file mode 100644
index 000000000..f036795d5
--- /dev/null
+++ b/pyload/plugin/crypter/NetfolderIn.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class NetfolderIn(SimpleCrypter):
+ __name = "NetfolderIn"
+ __type = "crypter"
+ __version = "0.72"
+
+ __pattern = r'http://(?:www\.)?netfolder\.in/(folder\.php\?folder_id=)?(?P<ID>\w+)(?(1)|/\w+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """NetFolder.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ NAME_PATTERN = r'<div class="Text">Inhalt des Ordners <span.*>(?P<N>.+)</span></div>'
+
+
+ def prepare(self):
+ super(NetfolderIn, self).prepare()
+
+ # 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"))
+
+
+ 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 = m.group('ID')
+ 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/plugin/crypter/NosvideoCom.py b/pyload/plugin/crypter/NosvideoCom.py
new file mode 100644
index 000000000..2afc697f3
--- /dev/null
+++ b/pyload/plugin/crypter/NosvideoCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class NosvideoCom(SimpleCrypter):
+ __name = "NosvideoCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?nosvideo\.com/\?v=\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Nosvideo.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ LINK_PATTERN = r'href="(http://(?:w{3}\.)?nosupload\.com/\?d=\w+)"'
+ NAME_PATTERN = r'<[tT]itle>Watch (?P<N>.+?)<'
diff --git a/pyload/plugin/crypter/OneKhDe.py b/pyload/plugin/crypter/OneKhDe.py
new file mode 100644
index 000000000..9d21710de
--- /dev/null
+++ b/pyload/plugin/crypter/OneKhDe.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import html_unescape
+
+from pyload.plugin.Crypter import Crypter
+
+
+class OneKhDe(Crypter):
+ __name = "OneKhDe"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?1kh\.de/f/'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """1kh.de decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
+
+
+ def __init__(self, parent):
+ Crypter.__init__(self, parent)
+ self.parent = parent
+
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ return True
+
+
+ def proceed(self, url, location):
+ url = self.parent.url
+ self.html = self.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.load("http://1kh.de/l/" + id)).group(1))
+ self.urls.append(new_link)
diff --git a/pyload/plugin/crypter/OronCom.py b/pyload/plugin/crypter/OronCom.py
new file mode 100644
index 000000000..316e6d525
--- /dev/null
+++ b/pyload/plugin/crypter/OronCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class OronCom(DeadCrypter):
+ __name = "OronCom"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?oron\.com/folder/\w+'
+ __config = []
+
+ __description = """Oron.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("DHMH", "webmaster@pcProfil.de")]
diff --git a/pyload/plugin/crypter/PastebinCom.py b/pyload/plugin/crypter/PastebinCom.py
new file mode 100644
index 000000000..034c859a1
--- /dev/null
+++ b/pyload/plugin/crypter/PastebinCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class PastebinCom(SimpleCrypter):
+ __name = "PastebinCom"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?pastebin\.com/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Pastebin.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ LINK_PATTERN = r'<div class="de\d+">(https?://[^ <]+)(?:[^<]*)</div>'
+ NAME_PATTERN = r'<div class="paste_box_line1" title="(?P<N>.+?)">'
diff --git a/pyload/plugin/crypter/QuickshareCz.py b/pyload/plugin/crypter/QuickshareCz.py
new file mode 100644
index 000000000..201fc1f6d
--- /dev/null
+++ b/pyload/plugin/crypter/QuickshareCz.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class QuickshareCz(Crypter):
+ __name = "QuickshareCz"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?quickshare\.cz/slozka-\d+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Quickshare.cz folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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.S)
+ if m is None:
+ self.error(_("FOLDER_PATTERN not found"))
+ self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
diff --git a/pyload/plugin/crypter/RSLayerCom.py b/pyload/plugin/crypter/RSLayerCom.py
new file mode 100644
index 000000000..756c356f7
--- /dev/null
+++ b/pyload/plugin/crypter/RSLayerCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class RSLayerCom(DeadCrypter):
+ __name = "RSLayerCom"
+ __type = "crypter"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?rs-layer\.com/directory-'
+ __config = []
+
+ __description = """RS-Layer.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("hzpz", "")]
diff --git a/pyload/plugin/crypter/RelinkUs.py b/pyload/plugin/crypter/RelinkUs.py
new file mode 100644
index 000000000..3ffc33c12
--- /dev/null
+++ b/pyload/plugin/crypter/RelinkUs.py
@@ -0,0 +1,294 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import binascii
+import re
+import os
+
+import Crypto
+
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class RelinkUs(Crypter):
+ __name = "RelinkUs"
+ __type = "crypter"
+ __version = "3.12"
+
+ __pattern = r'http://(?:www\.)?relink\.us/(f/|((view|go)\.php\?id=))(?P<ID>.+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Relink.us decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("AndroKev", "neureither.kevin@gmail.com")]
+
+
+ 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>(.*)</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\(\'(.+)\'\)'
+ WEB_FORWARD_URL = r'http://www.relink.us/frame.php'
+ WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(.+)"></iframe>'
+
+
+ def setup(self):
+ self.fileid = None
+ self.package = 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)]
+
+
+ def initPackage(self, pyfile):
+ self.fileid = re.match(self.__pattern, pyfile.url).group('ID')
+ self.package = pyfile.package()
+
+
+ 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):
+ password = self.getPassword()
+
+ self.logDebug("Submitting password [%s] for protected links" % password)
+
+ if password:
+ passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid
+ passwd_data = {'id': self.fileid, 'password': 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:
+ 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.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.error(_('Unknown source type "%s"') % source)
+
+
+ def handleCNL2Links(self):
+ self.logDebug("Search for CNL2 links")
+ package_links = []
+ m = re.search(self.CNL2_FORM_REGEX, self.html, re.S)
+ if m:
+ 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 Exception:
+ 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:
+ 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 = fs_join(self.config.get("general", "download_folder"), dlc_filename)
+ with open(dlc_filepath, "wb") as f:
+ f.write(dlc)
+ package_links.append(dlc_filepath)
+
+ except Exception:
+ self.fail(_("Unable to download DLC container"))
+
+ return package_links
+
+
+ def handleWEBLinks(self):
+ self.logDebug("Search for WEB links")
+
+ package_links = []
+ params = re.findall(self.WEB_FORWARD_REGEX, self.html)
+
+ self.logDebug("Decrypting %d Web links" % len(params))
+
+ for index, param in enumerate(params):
+ try:
+ url = self.WEB_FORWARD_URL + "?%s" % param
+
+ self.logDebug("Decrypting Web link %d, %s" % (index + 1, url))
+
+ res = self.load(url, decode=True)
+ link = re.search(self.WEB_LINK_REGEX, res).group(1)
+
+ package_links.append(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.I)
+
+ # Get crypted
+ crypted_re = self.CNL2_FORMINPUT_REGEX % RelinkUs.CNL2_CRYPTED_KEY
+ vcrypted = re.findall(crypted_re, cnl2_form, re.I)
+
+ # 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)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = Crypto.Cipher.AES.new(Key, Crypto.Cipher.AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugin/crypter/SafelinkingNet.py b/pyload/plugin/crypter/SafelinkingNet.py
new file mode 100644
index 000000000..71f41469b
--- /dev/null
+++ b/pyload/plugin/crypter/SafelinkingNet.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+import BeautifulSoup
+
+from pyload.utils import json_loads
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+
+
+class SafelinkingNet(Crypter):
+ __name = "SafelinkingNet"
+ __type = "crypter"
+ __version = "0.14"
+
+ __pattern = r'https?://(?:www\.)?safelinking\.net/([pd])/\w+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Safelinking.net decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("quareevo", "quareevo@arcor.de")]
+
+
+ SOLVEMEDIA_PATTERN = "solvemediaApiKey = '([\w.-]+)';"
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+
+ if re.match(self.__pattern, url).group(1) == "d":
+
+ header = self.load(url, just_header=True)
+ if 'location' in header:
+ self.urls = [header['location']]
+ else:
+ self.error(_("Couldn't find forwarded Link"))
+
+ else:
+ postData = {"post-protect": "1"}
+
+ self.html = self.load(url)
+
+ if "link-password" in self.html:
+ postData['link-password'] = self.getPassword()
+
+ if "altcaptcha" in self.html:
+ for _i 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"))
+
+ response, challenge = 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.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/plugin/crypter/SecuredIn.py b/pyload/plugin/crypter/SecuredIn.py
new file mode 100644
index 000000000..23bcea8ab
--- /dev/null
+++ b/pyload/plugin/crypter/SecuredIn.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.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'
+ __config = []
+
+ __description = """Secured.in decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/crypter/SexuriaCom.py b/pyload/plugin/crypter/SexuriaCom.py
new file mode 100644
index 000000000..7a104ce38
--- /dev/null
+++ b/pyload/plugin/crypter/SexuriaCom.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Crypter import Crypter
+
+
+class SexuriaCom(Crypter):
+ __name = "SexuriaCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?sexuria\.com/(v1/)?(Pornos_Kostenlos_.+?_(\d+)\.html|dl_links_\d+_\d+\.html|id=\d+\&part=\d+\&link=\d+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Sexuria.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("NETHead", "NETHead.AT.gmx.DOT.net")]
+
+
+ PATTERN_SUPPORTED_MAIN = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?Pornos_Kostenlos_.+?_(\d+)\.html', re.I)
+ PATTERN_SUPPORTED_CRYPT = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?dl_links_\d+_(?P<ID>\d+)\.html', re.I)
+ PATTERN_SUPPORTED_REDIRECT = re.compile(r'http://(www\.)?sexuria\.com/out\.php\?id=(?P<ID>\d+)\&part=\d+\&link=\d+', re.I)
+ PATTERN_TITLE = re.compile(r'<title> - (?P<TITLE>.*) Sexuria - Kostenlose Pornos - Rapidshare XXX Porn</title>', re.I)
+ PATTERN_PASSWORD = re.compile(r'<strong>Passwort: </strong></div></td>.*?bgcolor="#EFEFEF">(?P<PWD>.*?)</td>', re.I | re.S)
+ PATTERN_DL_LINK_PAGE = re.compile(r'"(dl_links_\d+_\d+\.html)"', re.I)
+ PATTERN_REDIRECT_LINKS = re.compile(r'value="(http://sexuria\.com/out\.php\?id=\d+\&part=\d+\&link=\d+)" readonly', re.I)
+
+
+ def decrypt(self, pyfile):
+ # Init
+ self.pyfile = pyfile
+ self.package = pyfile.package()
+
+ # Get package links
+ package_name, self.links, folder_name, package_pwd = self.decryptLinks(self.pyfile.url)
+ self.packages = [(package_name, self.links, folder_name)]
+
+
+ def decryptLinks(self, url):
+ linklist = []
+ name = self.package.name
+ folder = self.package.folder
+ password = None
+
+ if re.match(self.PATTERN_SUPPORTED_MAIN, url):
+ # Processing main page
+ html = self.load(url)
+ links = re.findall(self.PATTERN_DL_LINK_PAGE, html)
+ for link in links:
+ linklist.append("http://sexuria.com/v1/" + link)
+
+ elif re.match(self.PATTERN_SUPPORTED_REDIRECT, url):
+ # Processing direct redirect link (out.php), redirecting to main page
+ id = re.search(self.PATTERN_SUPPORTED_REDIRECT, url).group('ID')
+ if id:
+ linklist.append("http://sexuria.com/v1/Pornos_Kostenlos_liebe_%s.html" % id)
+
+ elif re.match(self.PATTERN_SUPPORTED_CRYPT, url):
+ # Extract info from main file
+ id = re.search(self.PATTERN_SUPPORTED_CRYPT, url).group('ID')
+ html = self.load("http://sexuria.com/v1/Pornos_Kostenlos_info_%s.html" % id, decode=True)
+
+ title = re.search(self.PATTERN_TITLE, html).group('TITLE').strip()
+ if title:
+ name = folder = title
+ self.logDebug("Package info found, name [%s] and folder [%s]" % (name, folder))
+
+ pwd = re.search(self.PATTERN_PASSWORD, html).group('PWD')
+ if pwd:
+ password = pwd.strip()
+ self.logDebug("Password info [%s] found" % password)
+
+ # Process link (dl_link)
+ html = self.load(url)
+ links = re.findall(self.PATTERN_REDIRECT_LINKS, html)
+ if len(links) == 0:
+ self.LogError("Broken for link %s" % link)
+ else:
+ for link in links:
+ link = link.replace("http://sexuria.com/", "http://www.sexuria.com/")
+ finallink = self.load(link, just_header=True)['location']
+ if not finallink or "sexuria.com/" in finallink:
+ self.LogError("Broken for link %s" % link)
+ else:
+ linklist.append(finallink)
+
+ # Debug log
+ self.logDebug("%d supported links" % len(linklist))
+ for i, link in enumerate(linklist):
+ self.logDebug("Supported link %d, %s" % (i + 1, link))
+
+ return name, linklist, folder, password
diff --git a/pyload/plugin/crypter/ShareLinksBiz.py b/pyload/plugin/crypter/ShareLinksBiz.py
new file mode 100644
index 000000000..25e891f3b
--- /dev/null
+++ b/pyload/plugin/crypter/ShareLinksBiz.py
@@ -0,0 +1,280 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import re
+
+import Crypto
+
+from pyload.plugin.Crypter import Crypter
+
+
+class ShareLinksBiz(Crypter):
+ __name = "ShareLinksBiz"
+ __type = "crypter"
+ __version = "1.14"
+
+ __pattern = r'http://(?:www\.)?(share-links|s2l)\.biz/(?P<ID>_?\w+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Share-Links.biz decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ def setup(self):
+ self.baseUrl = None
+ self.fileId = None
+ self.package = 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.invalidCaptcha()
+ self.retry(wait_time=5)
+ 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.iteritems():
+ 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.invalidCaptcha()
+ self.retry(wait_time=5)
+ 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.S)
+ if m:
+ 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
+ res = self.load(dwLink)
+
+ code = re.search(r'frm/(\d+)', res).group(1)
+ fwLink = self.baseUrl + "/get/frm/" + code
+ res = self.load(fwLink)
+
+ jscode = re.search(r'<script language="javascript">\s*eval\((.*)\)\s*</script>', res, re.S).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 Exception:
+ 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)
+ res = self.load(url)
+ params = res.split(";;")
+
+ # Get jk
+ strlist = list(params[1].decode('base64'))
+ jk = ''.join(strlist[::-1])
+
+ # Get crypted
+ strlist = list(params[2].decode('base64'))
+ crypted = ''.join(strlist[::-1])
+
+ # 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)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = Crypto.Cipher.AES.new(Key, Crypto.Cipher.AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted.decode('base64'))
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = filter(bool, text.split('\n'))
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugin/crypter/SharingmatrixCom.py b/pyload/plugin/crypter/SharingmatrixCom.py
new file mode 100644
index 000000000..4224bbd2d
--- /dev/null
+++ b/pyload/plugin/crypter/SharingmatrixCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class SharingmatrixCom(DeadCrypter):
+ __name = "SharingmatrixCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?sharingmatrix\.com/folder/\w+'
+ __config = []
+
+ __description = """Sharingmatrix.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/SpeedLoadOrg.py b/pyload/plugin/crypter/SpeedLoadOrg.py
new file mode 100644
index 000000000..32677e9ef
--- /dev/null
+++ b/pyload/plugin/crypter/SpeedLoadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class SpeedLoadOrg(DeadCrypter):
+ __name = "SpeedLoadOrg"
+ __type = "crypter"
+ __version = "0.30"
+
+ __pattern = r'http://(?:www\.)?speedload\.org/(\d+~f$|folder/\d+/)'
+ __config = []
+
+ __description = """Speedload decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/crypter/StealthTo.py b/pyload/plugin/crypter/StealthTo.py
new file mode 100644
index 000000000..b974a169c
--- /dev/null
+++ b/pyload/plugin/crypter/StealthTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class StealthTo(DeadCrypter):
+ __name = "StealthTo"
+ __type = "crypter"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?stealth\.to/folder/.+'
+ __config = []
+
+ __description = """Stealth.to decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
diff --git a/pyload/plugin/crypter/TnyCz.py b/pyload/plugin/crypter/TnyCz.py
new file mode 100644
index 000000000..8de611a8e
--- /dev/null
+++ b/pyload/plugin/crypter/TnyCz.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+import re
+
+
+class TnyCz(SimpleCrypter):
+ __name = "TnyCz"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?tny\.cz/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Tny.cz decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<title>(?P<N>.+) - .+</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/plugin/crypter/TrailerzoneInfo.py b/pyload/plugin/crypter/TrailerzoneInfo.py
new file mode 100644
index 000000000..cad464d47
--- /dev/null
+++ b/pyload/plugin/crypter/TrailerzoneInfo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class TrailerzoneInfo(DeadCrypter):
+ __name = "TrailerzoneInfo"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?trailerzone\.info/.+'
+ __config = []
+
+ __description = """TrailerZone.info decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
diff --git a/pyload/plugin/crypter/TurbobitNet.py b/pyload/plugin/crypter/TurbobitNet.py
new file mode 100644
index 000000000..d00380d71
--- /dev/null
+++ b/pyload/plugin/crypter/TurbobitNet.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+from pyload.utils import json_loads
+
+
+class TurbobitNet(SimpleCrypter):
+ __name = "TurbobitNet"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?turbobit\.net/download/folder/(?P<ID>\w+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Turbobit.net folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'src=\'/js/lib/grid/icon/folder.png\'> <span>(?P<N>.+?)</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/plugin/crypter/TusfilesNet.py b/pyload/plugin/crypter/TusfilesNet.py
new file mode 100644
index 000000000..53fde5643
--- /dev/null
+++ b/pyload/plugin/crypter/TusfilesNet.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import math
+import re
+import urlparse
+
+from pyload.plugin.internal.XFSCrypter import XFSCrypter
+
+
+class TusfilesNet(XFSCrypter):
+ __name = "TusfilesNet"
+ __type = "crypter"
+ __version = "0.08"
+
+ __pattern = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Tusfiles.net folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ PAGES_PATTERN = r'>\((\d+) \w+\)<'
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'https://www.tusfiles.net/go/\g<ID>/')]
+
+
+ def loadPage(self, page_n):
+ return self.load(urlparse.urljoin(self.pyfile.url, str(page_n)), decode=True)
+
+
+ def handle_pages(self, pyfile):
+ 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.links += self.getLinks()
diff --git a/pyload/plugin/crypter/UlozTo.py b/pyload/plugin/crypter/UlozTo.py
new file mode 100644
index 000000000..f2f6745c2
--- /dev/null
+++ b/pyload/plugin/crypter/UlozTo.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.Crypter import Crypter
+
+
+class UlozTo(Crypter):
+ __name = "UlozTo"
+ __type = "crypter"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(m|soubory)/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Uloz.to folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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.S)
+ if m is None:
+ self.error(_("FOLDER_PATTERN not found"))
+
+ 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)]
diff --git a/pyload/plugin/crypter/UploadableCh.py b/pyload/plugin/crypter/UploadableCh.py
new file mode 100644
index 000000000..45e57deb2
--- /dev/null
+++ b/pyload/plugin/crypter/UploadableCh.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class UploadableCh(SimpleCrypter):
+ __name = "UploadableCh"
+ __type = "crypter"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?uploadable\.ch/list/\w+'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Uploadable.ch folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LINK_PATTERN = r'"(.+?)" class="icon_zipfile">'
+ NAME_PATTERN = r'<div class="folder"><span>&nbsp;</span>(?P<N>.+?)</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/plugin/crypter/UploadedTo.py b/pyload/plugin/crypter/UploadedTo.py
new file mode 100644
index 000000000..895579a26
--- /dev/null
+++ b/pyload/plugin/crypter/UploadedTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class UploadedTo(SimpleCrypter):
+ __name = "UploadedTo"
+ __type = "crypter"
+ __version = "0.42"
+
+ __pattern = r'http://(?:www\.)?(uploaded|ul)\.(to|net)/(f|folder|list)/(?P<ID>\w+)'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """UploadedTo decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ PLAIN_PATTERN = r'<small class="date"><a href="([\w/]+)" onclick='
+ NAME_PATTERN = r'<title>(?P<N>.+?)<'
+
+
+ def getLinks(self):
+ m = re.search(self.PLAIN_PATTERN, self.html)
+ if m is None:
+ self.error(_("PLAIN_PATTERN not found"))
+
+ plain_link = urlparse.urljoin("http://uploaded.net/", m.group(1))
+ return self.load(plain_link).split('\n')[:-1]
diff --git a/pyload/plugin/crypter/WiiReloadedOrg.py b/pyload/plugin/crypter/WiiReloadedOrg.py
new file mode 100644
index 000000000..1fed5e4d2
--- /dev/null
+++ b/pyload/plugin/crypter/WiiReloadedOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.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=.+'
+ __config = []
+
+ __description = """Wii-Reloaded.org decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("hzpz", "")]
diff --git a/pyload/plugin/crypter/WuploadCom.py b/pyload/plugin/crypter/WuploadCom.py
new file mode 100644
index 000000000..29e2c97c5
--- /dev/null
+++ b/pyload/plugin/crypter/WuploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadCrypter import DeadCrypter
+
+
+class WuploadCom(DeadCrypter):
+ __name = "WuploadCom"
+ __type = "crypter"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?wupload\.com/folder/\w+'
+ __config = []
+
+ __description = """Wupload.com folder decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/crypter/XFileSharingPro.py b/pyload/plugin/crypter/XFileSharingPro.py
new file mode 100644
index 000000000..b78d83262
--- /dev/null
+++ b/pyload/plugin/crypter/XFileSharingPro.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSCrypter import XFSCrypter
+
+
+class XFileSharingPro(XFSCrypter):
+ __name = "XFileSharingPro"
+ __type = "crypter"
+ __version = "0.05"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """XFileSharingPro dummy folder decrypter plugin for hook"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def _log(self, type, args):
+ msg = " | ".join(str(a).strip() for a in args if a)
+ logger = getattr(self.log, type)
+ logger("%s: %s: %s" % (self.getClassName(), self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
+
+
+ def init(self):
+ super(XFileSharingPro, self).init()
+
+ self.__pattern = self.core.pluginManager.crypterPlugins[self.getClassName()]['pattern']
+
+ self.HOSTER_DOMAIN = re.match(self.__pattern, self.pyfile.url).group("DOMAIN").lower()
+ self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
+
+ account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
+
+ if account and account.canUse():
+ self.account = account
+
+ elif self.account:
+ self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
+
+ else:
+ return
+
+ self.user, data = self.account.selectAccount()
+ self.req = self.account.getAccountRequest(self.user)
+ self.premium = self.account.isPremium(self.user)
diff --git a/pyload/plugin/crypter/XupPl.py b/pyload/plugin/crypter/XupPl.py
new file mode 100644
index 000000000..6dd9c3df8
--- /dev/null
+++ b/pyload/plugin/crypter/XupPl.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter
+
+
+class XupPl(Crypter):
+ __name = "XupPl"
+ __type = "crypter"
+ __version = "0.10"
+
+ __pattern = r'https?://(?:[^/]*\.)?xup\.pl/.+'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Xup.pl decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "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/plugin/crypter/YoutubeComFolder.py b/pyload/plugin/crypter/YoutubeComFolder.py
new file mode 100644
index 000000000..6873c9148
--- /dev/null
+++ b/pyload/plugin/crypter/YoutubeComFolder.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.utils import json_loads
+from pyload.plugin.Crypter import Crypter
+from pyload.utils import fs_join
+
+
+class YoutubeComFolder(Crypter):
+ __name = "YoutubeComFolder"
+ __type = "crypter"
+ __version = "1.01"
+
+ __pattern = r'https?://(?:www\.|m\.)?youtube\.com/(?P<TYPE>user|playlist|view_play_list)(/|.*?[?&](?:list|p)=)(?P<ID>[\w-]+)'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True ),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True ),
+ ("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"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_KEY = "AIzaSyCKnWLNlkX-L4oD1aEzqqhRw1zczeD6_k0"
+
+
+ def api_response(self, ref, req):
+ req.update({"key": self.API_KEY})
+ url = urlparse.urljoin("https://www.googleapis.com/youtube/v3/", ref)
+ html = self.load(url, get=req)
+ return json_loads(html)
+
+
+ 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 = fs_join(self.config.get("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/pyload/plugin/crypter/__init__.py b/pyload/plugin/crypter/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/crypter/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/extractor/SevenZip.py b/pyload/plugin/extractor/SevenZip.py
new file mode 100644
index 000000000..cf5f7bfb8
--- /dev/null
+++ b/pyload/plugin/extractor/SevenZip.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import subprocess
+
+from pyload.plugin.extractor.UnRar import ArchiveError, CRCError, PasswordError, UnRar, renice
+from pyload.utils import fs_encode, fs_join
+
+
+class SevenZip(UnRar):
+ __name = "SevenZip"
+ __type = "extractor"
+ __version = "0.11"
+
+ __description = """7-Zip extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("Michael Nowak" , ""),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ CMD = "7z"
+ NAME = __name__.rsplit('.', 1)[1]
+ VERSION = ""
+
+ EXTENSIONS = [".7z", ".xz", ".zip", ".gz", ".gzip", ".tgz", ".bz2", ".bzip2",
+ ".tbz2", ".tbz", ".tar", ".wim", ".swm", ".lzma", ".rar", ".cab",
+ ".arj", ".z", ".taz", ".cpio", ".rpm", ".deb", ".lzh", ".lha",
+ ".chm", ".chw", ".hxs", ".iso", ".msi", ".doc", ".xls", ".ppt",
+ ".dmg", ".xar", ".hfs", ".exe", ".ntfs", ".fat", ".vhd", ".mbr",
+ ".squashfs", ".cramfs", ".scap"]
+
+
+ #@NOTE: there are some more uncovered 7z formats
+ re_filelist = re.compile(r'([\d\:]+)\s+([\d\:]+)\s+([\w\.]+)\s+(\d+)\s+(\d+)\s+(.+)')
+ re_wrongpwd = re.compile(r'(Can not open encrypted archive|Wrong password|Encrypted\s+\=\s+\+)', re.I)
+ re_wrongcrc = re.compile(r'CRC Failed|Can not open file', re.I)
+ re_version = re.compile(r'7-Zip\s(?:\[64\]\s)?(\d+\.\d+)', re.I)
+
+
+ @classmethod
+ def isUsable(cls):
+ if os.name == "nt":
+ cls.CMD = os.path.join(pypath, "7z.exe")
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ else:
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+
+ m = cls.re_version.search(out)
+ cls.VERSION = m.group(1) if m else '(version unknown)'
+
+ return True
+
+
+ def verify(self, password):
+ # 7z can't distinguish crc and pw error in test
+ p = self.call_cmd("l", "-slt", fs_encode(self.filename))
+ out, err = p.communicate()
+
+ if self.re_wrongpwd.search(out):
+ raise PasswordError
+
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+
+ def check(self, password):
+ p = self.call_cmd("l", "-slt", fs_encode(self.filename))
+ out, err = p.communicate()
+
+ # check if output or error macthes the 'wrong password'-Regexp
+ if self.re_wrongpwd.search(out):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(out):
+ raise CRCError(_("Header protected"))
+
+
+ def repair(self):
+ return False
+
+
+ def extract(self, password=None):
+ command = "x" if self.fullpath else "e"
+
+ p = self.call_cmd(command, '-o' + self.out, fs_encode(self.filename), password=password)
+
+ renice(p.pid, self.renice)
+
+ # communicate and retrieve stderr
+ self._progress(p)
+ err = p.stderr.read().strip()
+
+ if err:
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ elif self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+ else: #: raise error if anything is on stderr
+ raise ArchiveError(err)
+
+ if p.returncode > 1:
+ raise ArchiveError(_("Process return code: %d") % p.returncode)
+
+ self.files = self.list(password)
+
+
+ def list(self, password=None):
+ command = "l" if self.fullpath else "l"
+
+ p = self.call_cmd(command, fs_encode(self.filename), password=password)
+ out, err = p.communicate()
+
+ if "Can not open" in err:
+ raise ArchiveError(_("Cannot open file"))
+
+ if p.returncode > 1:
+ raise ArchiveError(_("Process return code: %d") % p.returncode)
+
+ result = set()
+ for groups in self.re_filelist.findall(out):
+ f = groups[-1].strip()
+ result.add(fs_join(self.out, f))
+
+ return list(result)
+
+
+ def call_cmd(self, command, *xargs, **kwargs):
+ args = []
+
+ # overwrite flag
+ if self.overwrite:
+ 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.manager.logDebug(" ".join(call))
+
+ p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return p
diff --git a/pyload/plugin/extractor/UnRar.py b/pyload/plugin/extractor/UnRar.py
new file mode 100644
index 000000000..2efba9cd4
--- /dev/null
+++ b/pyload/plugin/extractor/UnRar.py
@@ -0,0 +1,243 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import string
+import subprocess
+
+from pyload.plugin.Extractor import Extractor, ArchiveError, CRCError, PasswordError
+from pyload.utils import fs_decode, fs_encode, fs_join
+
+
+def renice(pid, value):
+ if value and os.name != "nt":
+ try:
+ subprocess.Popen(["renice", str(value), str(pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
+
+ except Exception:
+ pass
+
+
+class UnRar(Extractor):
+ __name = "UnRar"
+ __type = "extractor"
+ __version = "1.20"
+
+ __description = """Rar extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("Immenz", "immenz@gmx.net")]
+
+
+ CMD = "unrar"
+ NAME = __name__.rsplit('.', 1)[1]
+ VERSION = ""
+ EXTENSIONS = [".rar"]
+
+
+ re_multipart = re.compile(r'\.(part|r)(\d+)(?:\.rar)?(\.rev|\.bad)?', re.I)
+
+ re_filefixed = re.compile(r'Building (.+)')
+ re_filelist = re.compile(r'^(.)(\s*[\w\.\-]+)\s+(\d+\s+)+(?:\d+\%\s+)?[\d\-]{8}\s+[\d\:]{5}', re.M | re.I)
+
+ re_wrongpwd = re.compile(r'password', re.I)
+ re_wrongcrc = re.compile(r'encrypted|damaged|CRC failed|checksum error|corrupt', re.I)
+
+ re_version = re.compile(r'(?:UN)?RAR\s(\d+\.\d+)', re.I)
+
+
+ @classmethod
+ def isUsable(cls):
+ if os.name == "nt":
+ try:
+ cls.CMD = os.path.join(pypath, "RAR.exe")
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ cls.NAME = "RAR"
+ cls.REPAIR = True
+
+ except OSError:
+ cls.CMD = os.path.join(pypath, "UnRAR.exe")
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ else:
+ try:
+ p = subprocess.Popen(["rar"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ cls.NAME = "RAR"
+ cls.REPAIR = True
+
+ except OSError: #: fallback to unrar
+ p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = p.communicate()
+
+ m = cls.re_version.search(out)
+ cls.VERSION = m.group(1) if m else '(version unknown)'
+
+ return True
+
+
+ @classmethod
+ def isMultipart(cls, filename):
+ return cls.re_multipart.search(filename) is not None
+
+
+ def verify(self, password):
+ p = self.call_cmd("t", "-v", fs_encode(self.filename), password=password)
+ self._progress(p)
+ err = p.stderr.read().strip()
+
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+
+ def check(self, password):
+ p = self.call_cmd("l", "-v", fs_encode(self.filename), password=password)
+ out, err = p.communicate()
+
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ if self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+ # output only used to check if passworded files are present
+ for attr in self.re_filelist.findall(out):
+ if attr[0].startswith("*"):
+ raise PasswordError
+
+
+ def repair(self):
+ p = self.call_cmd("rc", fs_encode(self.filename))
+
+ # communicate and retrieve stderr
+ self._progress(p)
+ err = p.stderr.read().strip()
+ if err or p.returncode:
+ return False
+ return True
+
+
+ def _progress(self, process):
+ s = ""
+ while True:
+ c = process.stdout.read(1)
+ # quit loop on eof
+ if not c:
+ break
+ # reading a percentage sign -> set progress and restart
+ if c == '%':
+ self.notifyProgress(int(s))
+ s = ""
+ # not reading a digit -> therefore restart
+ elif c not in string.digits:
+ s = ""
+ # add digit to progressstring
+ else:
+ s += c
+
+
+ def extract(self, password=None):
+ command = "x" if self.fullpath else "e"
+
+ p = self.call_cmd(command, fs_encode(self.filename), self.out, password=password)
+
+ renice(p.pid, self.renice)
+
+ # communicate and retrieve stderr
+ self._progress(p)
+ err = p.stderr.read().strip()
+
+ if err:
+ if self.re_wrongpwd.search(err):
+ raise PasswordError
+
+ elif self.re_wrongcrc.search(err):
+ raise CRCError(err)
+
+ else: #: raise error if anything is on stderr
+ raise ArchiveError(err)
+
+ if p.returncode:
+ raise ArchiveError(_("Process return code: %d") % p.returncode)
+
+ self.files = self.list(password)
+
+
+ def getDeleteFiles(self):
+ dir, name = os.path.split(self.filename)
+
+ # actually extracted file
+ files = [self.filename]
+
+ # eventually Multipart Files
+ files.extend(fs_join(dir, os.path.basename(file)) for file in filter(self.isMultipart, os.listdir(dir))
+ if re.sub(self.re_multipart, ".rar", name) == re.sub(self.re_multipart, ".rar", file))
+
+ return files
+
+
+ def list(self, password=None):
+ command = "vb" if self.fullpath else "lb"
+
+ p = self.call_cmd(command, "-v", fs_encode(self.filename), password=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.manager.logError(err.strip())
+
+ result = set()
+ if not self.fullpath and self.VERSION.startswith('5'):
+ # NOTE: Unrar 5 always list full path
+ for f in fs_decode(out).splitlines():
+ f = fs_join(self.out, os.path.basename(f.strip()))
+ if os.path.isfile(f):
+ result.add(fs_join(self.out, os.path.basename(f)))
+ else:
+ for f in fs_decode(out).splitlines():
+ f = f.strip()
+ result.add(fs_join(self.out, f))
+
+ return list(result)
+
+
+ def call_cmd(self, command, *xargs, **kwargs):
+ args = []
+
+ # overwrite flag
+ if self.overwrite:
+ args.append("-o+")
+ else:
+ args.append("-o-")
+ if self.delete != 'No':
+ args.append("-or")
+
+ for word in self.excludefiles:
+ args.append("-x'%s'" % word.strip())
+
+ # 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-")
+
+ if self.keepbroken:
+ args.append("-kb")
+
+ # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
+ call = [self.CMD, command] + args + list(xargs)
+
+ self.manager.logDebug(" ".join(call))
+
+ p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return p
diff --git a/pyload/plugin/extractor/UnZip.py b/pyload/plugin/extractor/UnZip.py
new file mode 100644
index 000000000..8bfd63480
--- /dev/null
+++ b/pyload/plugin/extractor/UnZip.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import sys
+import zipfile
+
+from pyload.plugin.Extractor import Extractor, ArchiveError, CRCError, PasswordError
+from pyload.utils import fs_encode
+
+
+class UnZip(Extractor):
+ __name = "UnZip"
+ __type = "extractor"
+ __version = "1.12"
+
+ __description = """Zip extractor plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ EXTENSIONS = [".zip", ".zip64"]
+ NAME = __name__.rsplit('.', 1)[1]
+ VERSION = "(python %s.%s.%s)" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
+
+
+ @classmethod
+ def isUsable(cls):
+ return sys.version_info[:2] >= (2, 6)
+
+
+ def list(self, password=None):
+ with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
+ z.setpassword(password)
+ return z.namelist()
+
+
+ def check(self, password):
+ pass
+
+
+ def verify(self):
+ with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
+ badfile = z.testzip()
+
+ if badfile:
+ raise CRCError(badfile)
+ else:
+ raise PasswordError
+
+
+ def extract(self, password=None):
+ try:
+ with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z:
+ z.setpassword(password)
+
+ badfile = z.testzip()
+
+ if badfile:
+ raise CRCError(badfile)
+ else:
+ z.extractall(self.out)
+
+ except (zipfile.BadZipfile, zipfile.LargeZipFile), e:
+ raise ArchiveError(e)
+
+ except RuntimeError, e:
+ if "encrypted" in e:
+ raise PasswordError
+ else:
+ raise ArchiveError(e)
+ else:
+ self.files = z.namelist()
diff --git a/pyload/plugin/extractor/__init__.py b/pyload/plugin/extractor/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/extractor/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/hook/AlldebridCom.py b/pyload/plugin/hook/AlldebridCom.py
new file mode 100644
index 000000000..141f86779
--- /dev/null
+++ b/pyload/plugin/hook/AlldebridCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class AlldebridCom(MultiHook):
+ __name = "AlldebridCom"
+ __type = "hook"
+ __version = "0.16"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
+ ("ssl" , "bool" , "Use HTTPS" , True )]
+
+ __description = """Alldebrid.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def getHosters(self):
+ https = "https" if self.getConfig('ssl') else "http"
+ html = self.getURL(https + "://www.alldebrid.com/api.php", get={'action': "get_host"}).replace("\"", "").strip()
+
+ return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/BypassCaptcha.py b/pyload/plugin/hook/BypassCaptcha.py
new file mode 100644
index 000000000..8e8a27d8c
--- /dev/null
+++ b/pyload/plugin/hook/BypassCaptcha.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+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.06"
+
+ __config = [("force", "bool", "Force BC even if client is connected", False),
+ ("passkey", "password", "Passkey", "")]
+
+ __description = """Send captchas to BypassCaptcha.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("Godofdream", "soilfcition@gmail.com"),
+ ("zoidberg" , "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 getCredits(self):
+ res = getURL(self.GETCREDITS_URL, post={"key": self.getConfig('passkey')})
+
+ data = dict(x.split(' ', 1) for x in res.splitlines())
+ return int(data['Left'])
+
+
+ def submit(self, captcha, captchaType="file", match=None):
+ req = getRequest()
+
+ # raise timeout threshold
+ req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
+
+ try:
+ res = req.load(self.SUBMIT_URL,
+ post={'vendor_key': self.PYLOAD_KEY,
+ 'key': self.getConfig('passkey'),
+ 'gen_task_id': "1",
+ 'file': (pycurl.FORM_FILE, captcha)},
+ multipart=True)
+ finally:
+ req.close()
+
+ data = dict(x.split(' ', 1) for x in res.splitlines())
+ if not data or "Value" not in data:
+ raise BypassCaptchaException(res)
+
+ result = data['Value']
+ ticket = data['TaskId']
+ self.logDebug("Result %s : %s" % (ticket, result))
+
+ return ticket, result
+
+
+ def respond(self, ticket, success):
+ try:
+ res = 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"), e)
+
+
+ def captchaTask(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.getClassName()
+ task.setWaiting(100)
+ self._processCaptcha(task)
+
+ else:
+ self.logInfo(_("Your %s account has not enough credits") % self.getClassName())
+
+
+ def captchaCorrect(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ self.respond(task.data['ticket'], True)
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ self.respond(task.data['ticket'], False)
+
+
+ @threaded
+ 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/plugin/hook/Captcha9Kw.py b/pyload/plugin/hook/Captcha9Kw.py
new file mode 100644
index 000000000..32aaba0f5
--- /dev/null
+++ b/pyload/plugin/hook/Captcha9Kw.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import base64
+import re
+import time
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL
+
+from pyload.plugin.Hook import Hook, threaded
+
+
+class Captcha9Kw(Hook):
+ __name = "Captcha9Kw"
+ __type = "hook"
+ __version = "0.28"
+
+ __config = [("ssl" , "bool" , "Use HTTPS" , True),
+ ("force" , "bool" , "Force captcha resolving even if client is connected" , True),
+ ("confirm" , "bool" , "Confirm Captcha (cost +6 credits)" , False),
+ ("captchaperhour", "int" , "Captcha per hour" , "9999"),
+ ("captchapermin" , "int" , "Captcha per minute" , "9999"),
+ ("prio" , "int" , "Priority (max 10)(cost +0 -> +10 credits)" , "0"),
+ ("queue" , "int" , "Max. Queue (max 999)" , "50"),
+ ("hoster_options", "string" , "Hoster options (format: pluginname:prio=1:selfsolfe=1:confirm=1:timeout=900|...)", "ShareonlineBiz:prio=0:timeout=999 | UploadedTo:prio=0:timeout=999"),
+ ("selfsolve" , "bool" , "Selfsolve (manually solve your captcha in your 9kw client if active)" , "0"),
+ ("passkey" , "password", "API key" , ""),
+ ("timeout" , "int" , "Timeout in seconds (min 60, max 3999)" , "900")]
+
+ __description = """Send captchas to 9kw.eu"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_URL = "http://www.9kw.eu/index.cgi"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def getCredits(self):
+ res = getURL(self.API_URL,
+ get={'apikey': self.getConfig('passkey'),
+ 'pyload': "1",
+ 'source': "pyload",
+ 'action': "usercaptchaguthaben"})
+
+ if res.isdigit():
+ self.logInfo(_("%s credits left") % res)
+ credits = self.info['credits'] = int(res)
+ return credits
+ else:
+ self.logError(res)
+ return 0
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ try:
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+
+ except IOError, e:
+ self.logError(e)
+ return
+
+ pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
+ option = {'min' : 2,
+ 'max' : 50,
+ 'phrase' : 0,
+ 'numeric' : 0,
+ 'case_sensitive': 0,
+ 'math' : 0,
+ 'prio' : min(max(self.getConfig('prio'), 0), 10),
+ 'confirm' : self.getConfig('confirm'),
+ 'timeout' : min(max(self.getConfig('timeout'), 300), 3999),
+ 'selfsolve' : self.getConfig('selfsolve'),
+ 'cph' : self.getConfig('captchaperhour'),
+ 'cpm' : self.getConfig('captchapermin')}
+
+ for opt in str(self.getConfig('hoster_options').split('|')):
+
+ details = map(str.strip, opt.split(':'))
+
+ if not details or details[0].lower() != pluginname.lower():
+ continue
+
+ for d in details:
+ hosteroption = d.split("=")
+
+ if len(hosteroption) < 2 or not hosteroption[1].isdigit():
+ continue
+
+ o = hosteroption[0].lower()
+ if o in option:
+ option[o] = hosteroption[1]
+
+ break
+
+ post_data = {'apikey' : self.getConfig('passkey'),
+ 'prio' : option['prio'],
+ 'confirm' : option['confirm'],
+ 'maxtimeout' : option['timeout'],
+ 'selfsolve' : option['selfsolve'],
+ 'captchaperhour': option['cph'],
+ 'captchapermin' : option['cpm'],
+ 'case-sensitive': option['case_sensitive'],
+ 'min_len' : option['min'],
+ 'max_len' : option['max'],
+ 'phrase' : option['phrase'],
+ 'numeric' : option['numeric'],
+ 'math' : option['math'],
+ 'oldsource' : pluginname,
+ 'pyload' : "1",
+ 'source' : "pyload",
+ 'base64' : "1",
+ 'mouse' : 1 if task.isPositional() else 0,
+ 'file-upload-01': base64.b64encode(data),
+ 'action' : "usercaptchaupload"}
+
+ for _i in xrange(5):
+ try:
+ res = getURL(self.API_URL, post=post_data)
+ except BadHeader, e:
+ time.sleep(3)
+ else:
+ if res and res.isdigit():
+ break
+ else:
+ self.logError(_("Bad upload: %s") % res)
+ return
+
+ self.logDebug(_("NewCaptchaID ticket: %s") % res, task.captchaFile)
+
+ task.data['ticket'] = res
+
+ for _i in xrange(int(self.getConfig('timeout') / 5)):
+ result = getURL(self.API_URL,
+ get={'apikey': self.getConfig('passkey'),
+ 'id' : res,
+ 'pyload': "1",
+ 'info' : "1",
+ 'source': "pyload",
+ 'action': "usercaptchacorrectdata"})
+
+ if not result or result == "NO DATA":
+ time.sleep(5)
+ else:
+ break
+ else:
+ self.logDebug("Could not send request: %s" % res)
+ result = None
+
+ self.logInfo(_("Captcha result for ticket %s: %s") % (res, result))
+
+ task.setResult(result)
+
+
+ def captchaTask(self, task):
+ if not task.isTextual() and not task.isPositional():
+ return
+
+ if not self.getConfig('passkey'):
+ return
+
+ if self.core.isClientConnected() and not self.getConfig('force'):
+ return
+
+ credits = self.getCredits()
+
+ if not credits:
+ self.logError(_("Your captcha 9kw.eu account has not enough credits"))
+ return
+
+ queue = min(self.getConfig('queue'), 999)
+ timeout = min(max(self.getConfig('timeout'), 300), 3999)
+ pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1)
+
+ for _i in xrange(5):
+ servercheck = getURL("http://www.9kw.eu/grafik/servercheck.txt")
+ if queue < re.search(r'queue=(\d+)', servercheck).group(1):
+ break
+
+ time.sleep(10)
+ else:
+ self.fail(_("Too many captchas in queue"))
+
+ for opt in str(self.getConfig('hoster_options').split('|')):
+ details = map(str.strip, opt.split(':'))
+
+ if not details or details[0].lower() != pluginname.lower():
+ continue
+
+ for d in details:
+ hosteroption = d.split("=")
+
+ if len(hosteroption) > 1 \
+ and hosteroption[0].lower() == 'timeout' \
+ and hosteroption[1].isdigit():
+ timeout = int(hosteroption[1])
+
+ break
+
+ task.handler.append(self)
+
+ task.setWaiting(timeout)
+
+ self._processCaptcha(task)
+
+
+ def _captchaResponse(self, task, correct):
+ type = "correct" if correct else "refund"
+
+ if 'ticket' not in task.data:
+ self.logDebug("No CaptchaID for %s request (task: %s)" % (type, task))
+ return
+
+ passkey = self.getConfig('passkey')
+
+ for _i in xrange(3):
+ res = getURL(self.API_URL,
+ get={'action' : "usercaptchacorrectback",
+ 'apikey' : passkey,
+ 'api_key': passkey,
+ 'correct': "1" if correct else "2",
+ 'pyload' : "1",
+ 'source' : "pyload",
+ 'id' : task.data['ticket']})
+
+ self.logDebug("Request %s: %s" % (type, res))
+
+ if res == "OK":
+ break
+
+ time.sleep(5)
+ else:
+ self.logDebug("Could not send %s request: %s" % (type, res))
+
+
+ def captchaCorrect(self, task):
+ self._captchaResponse(task, True)
+
+
+ def captchaInvalid(self, task):
+ self._captchaResponse(task, False)
diff --git a/pyload/plugin/hook/CaptchaBrotherhood.py b/pyload/plugin/hook/CaptchaBrotherhood.py
new file mode 100644
index 000000000..75c7d55b7
--- /dev/null
+++ b/pyload/plugin/hook/CaptchaBrotherhood.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import StringIO
+import pycurl
+import time
+import urllib
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+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.08"
+
+ __config = [("username", "str", "Username", ""),
+ ("force", "bool", "Force CT even if client is connected", False),
+ ("passkey", "password", "Password", "")]
+
+ __description = """Send captchas to CaptchaBrotherhood.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://www.captchabrotherhood.com/"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def getCredits(self):
+ res = getURL(self.API_URL + "askCredits.aspx",
+ get={"username": self.getConfig('username'), "password": self.getConfig('passkey')})
+ if not res.startswith("OK"):
+ raise CaptchaBrotherhoodException(res)
+ else:
+ credits = int(res[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,
+ urllib.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()
+ res = req.getResponse()
+ except Exception, e:
+ raise CaptchaBrotherhoodException("Submit captcha image failed")
+
+ req.close()
+
+ if not res.startswith("OK"):
+ raise CaptchaBrotherhoodException(res[1])
+
+ ticket = res[3:]
+
+ for _i in xrange(15):
+ time.sleep(5)
+ res = self.api_response("askCaptchaResult", ticket)
+ if res.startswith("OK-answered"):
+ return ticket, res[12:]
+
+ raise CaptchaBrotherhoodException("No solution received in time")
+
+
+ def api_response(self, api, ticket):
+ res = getURL("%s%s.aspx" % (self.API_URL, api),
+ get={"username": self.getConfig('username'),
+ "password": self.getConfig('passkey'),
+ "captchaID": ticket})
+ if not res.startswith("OK"):
+ raise CaptchaBrotherhoodException("Unknown response: %s" % res)
+
+ return res
+
+
+ def captchaTask(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.getClassName()
+ task.setWaiting(100)
+ self._processCaptcha(task)
+ else:
+ self.logInfo(_("Your CaptchaBrotherhood Account has not enough credits"))
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ res = self.api_response("complainCaptcha", task.data['ticket'])
+
+
+ @threaded
+ 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/plugin/hook/DeathByCaptcha.py b/pyload/plugin/hook/DeathByCaptcha.py
new file mode 100644
index 000000000..ddcdeae8a
--- /dev/null
+++ b/pyload/plugin/hook/DeathByCaptcha.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import base64
+import re
+import time
+
+import pycurl
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+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.06"
+
+ __config = [("username", "str", "Username", ""),
+ ("passkey", "password", "Password", ""),
+ ("force", "bool", "Force DBC even if client is connected", False)]
+
+ __description = """Send captchas to DeathByCaptcha.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://api.dbcapi.me/api/"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def api_response(self, api="captcha", post=False, multipart=False):
+ req = getRequest()
+ req.c.setopt(pycurl.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')})
+
+ res = None
+ try:
+ json = req.load("%s%s" % (self.API_URL, api),
+ post=post,
+ multipart=multipart)
+ self.logDebug(json)
+ res = json_loads(json)
+
+ if "error" in res:
+ raise DeathByCaptchaException(res['error'])
+ elif "status" not in res:
+ raise DeathByCaptchaException(str(res))
+
+ 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 res
+
+
+ def getCredits(self):
+ res = self.api_response("user", True)
+
+ if 'is_banned' in res and res['is_banned']:
+ raise DeathByCaptchaException('banned')
+ elif 'balance' in res and 'rate' in res:
+ self.info.update(res)
+ else:
+ raise DeathByCaptchaException(res)
+
+
+ def getStatus(self):
+ res = self.api_response("status", False)
+
+ if 'is_service_overloaded' in res and res['is_service_overloaded']:
+ raise DeathByCaptchaException('service-overload')
+
+
+ def submit(self, captcha, captchaType="file", match=None):
+ #@NOTE: Workaround multipart-post bug in HTTPRequest.py
+ if re.match("^\w*$", self.getConfig('passkey')):
+ multipart = True
+ data = (pycurl.FORM_FILE, captcha)
+ else:
+ multipart = False
+ with open(captcha, 'rb') as f:
+ data = f.read()
+ data = "base64:" + base64.b64encode(data)
+
+ res = self.api_response("captcha", {"captchafile": data}, multipart)
+
+ if "captcha" not in res:
+ raise DeathByCaptchaException(res)
+ ticket = res['captcha']
+
+ for _i in xrange(24):
+ time.sleep(5)
+ res = self.api_response("captcha/%d" % ticket, False)
+ if res['text'] and res['is_correct']:
+ break
+ else:
+ raise DeathByCaptchaException('timed-out')
+
+ result = res['text']
+ self.logDebug("Result %s : %s" % (ticket, result))
+
+ return ticket, result
+
+
+ def captchaTask(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.getClassName()
+ task.setWaiting(180)
+ self._processCaptcha(task)
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ try:
+ res = self.api_response("captcha/%d/report" % task.data['ticket'], True)
+
+ except DeathByCaptchaException, e:
+ self.logError(e.getDesc())
+
+ except Exception, e:
+ self.logError(e)
+
+
+ @threaded
+ 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/plugin/hook/DebridItaliaCom.py b/pyload/plugin/hook/DebridItaliaCom.py
new file mode 100644
index 000000000..e15f7d6ec
--- /dev/null
+++ b/pyload/plugin/hook/DebridItaliaCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class DebridItaliaCom(MultiHook):
+ __name = "DebridItaliaCom"
+ __type = "hook"
+ __version = "0.12"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Debriditalia.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell" , "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ def getHosters(self):
+ return self.getURL("http://debriditalia.com/api.php", get={'hosts': ""}).replace('"', '').split(',')
diff --git a/pyload/plugin/hook/EasybytezCom.py b/pyload/plugin/hook/EasybytezCom.py
new file mode 100644
index 000000000..68b4d96d2
--- /dev/null
+++ b/pyload/plugin/hook/EasybytezCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class EasybytezCom(MultiHook):
+ __name = "EasybytezCom"
+ __type = "hook"
+ __version = "0.07"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """EasyBytez.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def getHosters(self):
+ user, data = self.account.selectAccount()
+
+ req = self.account.getAccountRequest(user)
+ html = req.load("http://www.easybytez.com")
+
+ return re.search(r'</textarea>\s*Supported sites:(.*)', html).group(1).split(',')
diff --git a/pyload/plugin/hook/ExpertDecoders.py b/pyload/plugin/hook/ExpertDecoders.py
new file mode 100644
index 000000000..2e05e7386
--- /dev/null
+++ b/pyload/plugin/hook/ExpertDecoders.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import base64
+import uuid
+
+import pycurl
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+class ExpertDecoders(Hook):
+ __name = "ExpertDecoders"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("force", "bool", "Force CT even if client is connected", False),
+ ("passkey", "password", "Access key", "")]
+
+ __description = """Send captchas to expertdecoders.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org" ),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ API_URL = "http://www.fasttypers.org/imagepost.ashx"
+
+
+ def activate(self):
+ if self.getConfig('ssl'):
+ self.API_URL = self.API_URL.replace("http://", "https://")
+
+
+ def getCredits(self):
+ res = getURL(self.API_URL, post={"key": self.getConfig('passkey'), "action": "balance"})
+
+ if res.isdigit():
+ self.logInfo(_("%s credits left") % res)
+ self.info['credits'] = credits = int(res)
+ return credits
+ else:
+ self.logError(res)
+ return 0
+
+
+ @threaded
+ def _processCaptcha(self, task):
+ task.data['ticket'] = ticket = uuid.uuid4()
+ result = None
+
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+
+ req = getRequest()
+ # raise timeout threshold
+ req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
+
+ try:
+ result = req.load(self.API_URL,
+ post={'action' : "upload",
+ 'key' : self.getConfig('passkey'),
+ 'file' : base64.b64encode(data),
+ 'gen_task_id': ticket})
+ finally:
+ req.close()
+
+ self.logDebug("Result %s : %s" % (ticket, result))
+ task.setResult(result)
+
+
+ def captchaTask(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)
+ self._processCaptcha(task)
+
+ else:
+ self.logInfo(_("Your ExpertDecoders Account has not enough credits"))
+
+
+ def captchaInvalid(self, task):
+ if "ticket" in task.data:
+
+ try:
+ res = getURL(self.API_URL,
+ post={'action': "refund", 'key': self.getConfig('passkey'), 'gen_task_id': task.data['ticket']})
+ self.logInfo(_("Request refund"), res)
+
+ except BadHeader, e:
+ self.logError(_("Could not send refund request"), e)
diff --git a/pyload/plugin/hook/FastixRu.py b/pyload/plugin/hook/FastixRu.py
new file mode 100644
index 000000000..6c71384c8
--- /dev/null
+++ b/pyload/plugin/hook/FastixRu.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class FastixRu(MultiHook):
+ __name = "FastixRu"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Fastix.ru hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def getHosters(self):
+ html = self.getURL("http://fastix.ru/api_v2",
+ get={'apikey': "5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y",
+ 'sub' : "allowed_sources"})
+ host_list = json_loads(html)
+ host_list = host_list['allow']
+ return host_list
diff --git a/pyload/plugin/hook/FreeWayMe.py b/pyload/plugin/hook/FreeWayMe.py
new file mode 100644
index 000000000..fd1017ba9
--- /dev/null
+++ b/pyload/plugin/hook/FreeWayMe.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class FreeWayMe(MultiHook):
+ __name = "FreeWayMe"
+ __type = "hook"
+ __version = "0.15"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """FreeWay.me hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def getHosters(self):
+ # Get account data
+ if not self.account or not self.account.canUse():
+ hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3}).replace("\"", "").strip()
+ else:
+ self.logDebug("AccountInfo available - Get HosterList with User Pass")
+ (user, data) = self.account.selectAccount()
+ hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3, "user": user, "pass": data['password']}).replace("\"", "").strip()
+
+ self.logDebug("hosters: %s" % hostis)
+ return [x.strip() for x in hostis.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/ImageTyperz.py b/pyload/plugin/hook/ImageTyperz.py
new file mode 100644
index 000000000..039710436
--- /dev/null
+++ b/pyload/plugin/hook/ImageTyperz.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import base64
+import re
+
+import pycurl
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugin.Hook import Hook, threaded
+
+
+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.06"
+
+ __config = [("username", "str", "Username", ""),
+ ("passkey", "password", "Password", ""),
+ ("force", "bool", "Force IT even if client is connected", False)]
+
+ __description = """Send captchas to ImageTyperz.com"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org"),
+ ("zoidberg", "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 getCredits(self):
+ res = getURL(self.GETCREDITS_URL,
+ post={'action': "REQUESTBALANCE",
+ 'username': self.getConfig('username'),
+ 'password': self.getConfig('passkey')})
+
+ if res.startswith('ERROR'):
+ raise ImageTyperzException(res)
+
+ try:
+ balance = float(res)
+ except Exception:
+ raise ImageTyperzException("Invalid response")
+
+ self.logInfo(_("Account balance: $%s left") % res)
+ return balance
+
+
+ def submit(self, captcha, captchaType="file", match=None):
+ req = getRequest()
+ # raise timeout threshold
+ req.c.setopt(pycurl.LOW_SPEED_TIME, 80)
+
+ try:
+ #@NOTE: Workaround multipart-post bug in HTTPRequest.py
+ if re.match("^\w*$", self.getConfig('passkey')):
+ multipart = True
+ data = (pycurl.FORM_FILE, captcha)
+ else:
+ multipart = False
+ with open(captcha, 'rb') as f:
+ data = f.read()
+ data = base64.b64encode(data)
+
+ res = req.load(self.SUBMIT_URL,
+ post={'action': "UPLOADCAPTCHA",
+ 'username': self.getConfig('username'),
+ 'password': self.getConfig('passkey'), "file": data},
+ multipart=multipart)
+ finally:
+ req.close()
+
+ if res.startswith("ERROR"):
+ raise ImageTyperzException(res)
+ else:
+ data = res.split('|')
+ if len(data) == 2:
+ ticket, result = data
+ else:
+ raise ImageTyperzException("Unknown response: %s" % res)
+
+ return ticket, result
+
+
+ def captchaTask(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.getClassName()
+ task.setWaiting(100)
+ self._processCaptcha(task)
+
+ else:
+ self.logInfo(_("Your %s account has not enough credits") % self.getClassName())
+
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.getClassName() and "ticket" in task.data:
+ res = getURL(self.RESPOND_URL,
+ post={'action': "SETBADIMAGE",
+ 'username': self.getConfig('username'),
+ 'password': self.getConfig('passkey'),
+ 'imageid': task.data['ticket']})
+
+ if res == "SUCCESS":
+ self.logInfo(_("Bad captcha solution received, requested refund"))
+ else:
+ self.logError(_("Bad captcha solution received, refund request failed"), res)
+
+
+ @threaded
+ 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/plugin/hook/LinkdecrypterCom.py b/pyload/plugin/hook/LinkdecrypterCom.py
new file mode 100644
index 000000000..a4a125236
--- /dev/null
+++ b/pyload/plugin/hook/LinkdecrypterCom.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class LinkdecrypterCom(MultiHook):
+ __name = "LinkdecrypterCom"
+ __type = "hook"
+ __version = "1.06"
+
+ __config = [("activated" , "bool" , "Activated" , True ),
+ ("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Linkdecrypter.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def getHosters(self):
+ list = re.search(r'>Supported\(\d+\)</b>: <i>(.[\w.\-, ]+)',
+ self.getURL("http://linkdecrypter.com/", decode=True).replace("(g)", "")).group(1).split(', ')
+ try:
+ list.remove("download.serienjunkies.org")
+ except ValueError:
+ pass
+
+ return list
diff --git a/pyload/plugin/hook/LinksnappyCom.py b/pyload/plugin/hook/LinksnappyCom.py
new file mode 100644
index 000000000..e09021d6d
--- /dev/null
+++ b/pyload/plugin/hook/LinksnappyCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class LinksnappyCom(MultiHook):
+ __name = "LinksnappyCom"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Linksnappy.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHosters(self):
+ json_data = self.getURL("http://gen.linksnappy.com/lseAPI.php", get={'act': "FILEHOSTS"})
+ json_data = json_loads(json_data)
+
+ return json_data['return'].keys()
diff --git a/pyload/plugin/hook/MegaDebridEu.py b/pyload/plugin/hook/MegaDebridEu.py
new file mode 100644
index 000000000..41abce37b
--- /dev/null
+++ b/pyload/plugin/hook/MegaDebridEu.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class MegaDebridEu(MultiHook):
+ __name = "MegaDebridEu"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Mega-debrid.eu hook plugin"""
+ __license = "GPLv3"
+ __authors = [("D.Ducatel", "dducatel@je-geek.fr")]
+
+
+ def getHosters(self):
+ reponse = self.getURL("http://www.mega-debrid.eu/api.php", get={'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 = []
+
+ return host_list
diff --git a/pyload/plugin/hook/MegaRapidoNet.py b/pyload/plugin/hook/MegaRapidoNet.py
new file mode 100644
index 000000000..4650d8b6d
--- /dev/null
+++ b/pyload/plugin/hook/MegaRapidoNet.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class MegaRapidoNet(MultiHook):
+ __name = "MegaRapidoNet"
+ __type = "hook"
+ __version = "0.02"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)", "" ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """MegaRapido.net hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def getHosters(self):
+ hosters = {'1fichier' : [],#leave it there are so many possible addresses?
+ '1st-files' : ['1st-files.com'],
+ '2shared' : ['2shared.com'],
+ '4shared' : ['4shared.com', '4shared-china.com'],
+ 'asfile' : ['http://asfile.com/'],
+ 'bitshare' : ['bitshare.com'],
+ 'brupload' : ['brupload.net'],
+ 'crocko' : ['crocko.com','easy-share.com'],
+ 'dailymotion' : ['dailymotion.com'],
+ 'depfile' : ['depfile.com'],
+ 'depositfiles': ['depositfiles.com', 'dfiles.eu'],
+ 'dizzcloud' : ['dizzcloud.com'],
+ 'dl.dropbox' : [],
+ 'extabit' : ['extabit.com'],
+ 'extmatrix' : ['extmatrix.com'],
+ 'facebook' : [],
+ 'file4go' : ['file4go.com'],
+ 'filecloud' : ['filecloud.io','ifile.it','mihd.net'],
+ 'filefactory' : ['filefactory.com'],
+ 'fileom' : ['fileom.com'],
+ 'fileparadox' : ['fileparadox.in'],
+ 'filepost' : ['filepost.com', 'fp.io'],
+ 'filerio' : ['filerio.in','filerio.com','filekeen.com'],
+ 'filesflash' : ['filesflash.com'],
+ 'firedrive' : ['firedrive.com', 'putlocker.com'],
+ 'flashx' : [],
+ 'freakshare' : ['freakshare.net', 'freakshare.com'],
+ 'gigasize' : ['gigasize.com'],
+ 'hipfile' : ['hipfile.com'],
+ 'junocloud' : ['junocloud.me'],
+ 'letitbit' : ['letitbit.net','shareflare.net'],
+ 'mediafire' : ['mediafire.com'],
+ 'mega' : ['mega.co.nz'],
+ 'megashares' : ['megashares.com'],
+ 'metacafe' : ['metacafe.com'],
+ 'netload' : ['netload.in'],
+ 'oboom' : ['oboom.com'],
+ 'rapidgator' : ['rapidgator.net'],
+ 'rapidshare' : ['rapidshare.com'],
+ 'rarefile' : ['rarefile.net'],
+ 'ryushare' : ['ryushare.com'],
+ 'sendspace' : ['sendspace.com'],
+ 'turbobit' : ['turbobit.net', 'unextfiles.com'],
+ 'uploadable' : ['uploadable.ch'],
+ 'uploadbaz' : ['uploadbaz.com'],
+ 'uploaded' : ['uploaded.to', 'uploaded.net', 'ul.to'],
+ 'uploadhero' : ['uploadhero.com'],
+ 'uploading' : ['uploading.com'],
+ 'uptobox' : ['uptobox.com'],
+ 'xvideos' : ['xvideos.com'],
+ 'youtube' : ['youtube.com']}
+
+ hoster_list = []
+
+ for item in hosters.itervalues():
+ hoster_list.extend(item)
+
+ return hoster_list
diff --git a/pyload/plugin/hook/MultihostersCom.py b/pyload/plugin/hook/MultihostersCom.py
new file mode 100644
index 000000000..5cb2396ee
--- /dev/null
+++ b/pyload/plugin/hook/MultihostersCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hook.ZeveraCom import ZeveraCom
+
+
+class MultihostersCom(ZeveraCom):
+ __name = "MultihostersCom"
+ __type = "hook"
+ __version = "0.02"
+
+ __config = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed", "bool" , "Revert to standard download if download fails", False),
+ ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
+
+ __description = """Multihosters.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("tjeh", "tjeh@gmx.net")]
diff --git a/pyload/plugin/hook/MultishareCz.py b/pyload/plugin/hook/MultishareCz.py
new file mode 100644
index 000000000..39817d7ce
--- /dev/null
+++ b/pyload/plugin/hook/MultishareCz.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class MultishareCz(MultiHook):
+ __name = "MultishareCz"
+ __type = "hook"
+ __version = "0.07"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """MultiShare.cz hook plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="(.+?)"></td>\s*<td class="stav">[^>]*?alt="OK"'
+
+
+ def getHosters(self):
+ html = self.getURL("http://www.multishare.cz/monitoring/")
+ return re.findall(self.HOSTER_PATTERN, html)
diff --git a/pyload/plugin/hook/MyfastfileCom.py b/pyload/plugin/hook/MyfastfileCom.py
new file mode 100644
index 000000000..b6573e56d
--- /dev/null
+++ b/pyload/plugin/hook/MyfastfileCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(MultiHook):
+ __name = "MyfastfileCom"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Myfastfile.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHosters(self):
+ json_data = self.getURL("http://myfastfile.com/api.php", get={'hosts': ""}, decode=True)
+ self.logDebug("JSON data", json_data)
+ json_data = json_loads(json_data)
+
+ return json_data['hosts']
diff --git a/pyload/plugin/hook/NoPremiumPl.py b/pyload/plugin/hook/NoPremiumPl.py
new file mode 100644
index 000000000..527413a88
--- /dev/null
+++ b/pyload/plugin/hook/NoPremiumPl.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class NoPremiumPl(MultiHook):
+ __name = "NoPremiumPl"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """NoPremium.pl hook plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@nopremium.pl")]
+
+
+ def getHosters(self):
+ hostings = json_loads(self.getURL("https://www.nopremium.pl/clipboard.php?json=3").strip())
+ hostings_domains = [domain for row in hostings for domain in row['domains'] if row['sdownload'] == "0"]
+
+ self.logDebug(hostings_domains)
+
+ return hostings_domains
diff --git a/pyload/plugin/hook/OverLoadMe.py b/pyload/plugin/hook/OverLoadMe.py
new file mode 100644
index 000000000..5fc04e6f4
--- /dev/null
+++ b/pyload/plugin/hook/OverLoadMe.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class OverLoadMe(MultiHook):
+ __name = "OverLoadMe"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
+ ("ssl" , "bool" , "Use HTTPS" , True )]
+
+ __description = """Over-Load.me hook plugin"""
+ __license = "GPLv3"
+ __authors = [("marley", "marley@over-load.me")]
+
+
+ def getHosters(self):
+ https = "https" if self.getConfig('ssl') else "http"
+ html = self.getURL(https + "://api.over-load.me/hoster.php",
+ get={'auth': "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}).replace("\"", "").strip()
+ self.logDebug("Hosterlist", html)
+
+ return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/PremiumTo.py b/pyload/plugin/hook/PremiumTo.py
new file mode 100644
index 000000000..361f072a3
--- /dev/null
+++ b/pyload/plugin/hook/PremiumTo.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class PremiumTo(MultiHook):
+ __name = "PremiumTo"
+ __type = "hook"
+ __version = "0.08"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Premium.to hook plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN" , "RaNaN@pyload.org" ),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ def getHosters(self):
+ html = self.getURL("http://premium.to/api/hosters.php",
+ get={'username': self.account.username, 'password': self.account.password})
+ return [x.strip() for x in html.replace("\"", "").split(";")]
diff --git a/pyload/plugin/hook/PremiumizeMe.py b/pyload/plugin/hook/PremiumizeMe.py
new file mode 100644
index 000000000..1e4a58592
--- /dev/null
+++ b/pyload/plugin/hook/PremiumizeMe.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class PremiumizeMe(MultiHook):
+ __name = "PremiumizeMe"
+ __type = "hook"
+ __version = "0.17"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Premiumize.me hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")]
+
+
+ def getHosters(self):
+ # 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 = self.getURL("https://api.premiumize.me/pm-api/v1.php",
+ get={'method': "hosterlist", 'params[login]': user, 'params[pass]': 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']
diff --git a/pyload/plugin/hook/PutdriveCom.py b/pyload/plugin/hook/PutdriveCom.py
new file mode 100644
index 000000000..126eb86e8
--- /dev/null
+++ b/pyload/plugin/hook/PutdriveCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hook.ZeveraCom import ZeveraCom
+
+
+class PutdriveCom(ZeveraCom):
+ __name = "PutdriveCom"
+ __type = "hook"
+ __version = "0.01"
+
+ __config = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed", "bool" , "Revert to standard download if download fails", False),
+ ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )]
+
+ __description = """Putdrive.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hook/RPNetBiz.py b/pyload/plugin/hook/RPNetBiz.py
new file mode 100644
index 000000000..d9d3e6177
--- /dev/null
+++ b/pyload/plugin/hook/RPNetBiz.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RPNetBiz(MultiHook):
+ __name = "RPNetBiz"
+ __type = "hook"
+ __version = "0.14"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """RPNet.biz hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Dman", "dmanugm@gmail.com")]
+
+
+ def getHosters(self):
+ # Get account data
+ user, data = self.account.selectAccount()
+
+ res = self.getURL("https://premium.rpnet.biz/client_api.php",
+ get={'username': user, 'password': data['password'], 'action': "showHosterList"})
+ hoster_list = json_loads(res)
+
+ # 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']
diff --git a/pyload/plugin/hook/RapideoPl.py b/pyload/plugin/hook/RapideoPl.py
new file mode 100644
index 000000000..1761659db
--- /dev/null
+++ b/pyload/plugin/hook/RapideoPl.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RapideoPl(MultiHook):
+ __name = "RapideoPl"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Rapideo.pl hook plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@rapideo.pl")]
+
+
+ def getHosters(self):
+ hostings = json_loads(self.getURL("https://www.rapideo.pl/clipboard.php?json=3").strip())
+ hostings_domains = [domain for row in hostings for domain in row['domains'] if row['sdownload'] == "0"]
+
+ self.logDebug(hostings_domains)
+
+ return hostings_domains
diff --git a/pyload/plugin/hook/RealdebridCom.py b/pyload/plugin/hook/RealdebridCom.py
new file mode 100644
index 000000000..916737cd8
--- /dev/null
+++ b/pyload/plugin/hook/RealdebridCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RealdebridCom(MultiHook):
+ __name = "RealdebridCom"
+ __type = "hook"
+ __version = "0.46"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 ),
+ ("ssl" , "bool" , "Use HTTPS" , True )]
+
+ __description = """Real-Debrid.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def getHosters(self):
+ https = "https" if self.getConfig('ssl') else "http"
+ html = self.getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip()
+
+ return [x.strip() for x in html.split(",") if x.strip()]
diff --git a/pyload/plugin/hook/RehostTo.py b/pyload/plugin/hook/RehostTo.py
new file mode 100644
index 000000000..087701c5b
--- /dev/null
+++ b/pyload/plugin/hook/RehostTo.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class RehostTo(MultiHook):
+ __name = "RehostTo"
+ __type = "hook"
+ __version = "0.50"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Rehost.to hook plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def getHosters(self):
+ user, data = self.account.selectAccount()
+ html = self.getURL("http://rehost.to/api.php",
+ get={'cmd' : "get_supported_och_dl",
+ 'long_ses': self.account.getAccountInfo(user)['session']})
+ return [x.strip() for x in html.replace("\"", "").split(",")]
diff --git a/pyload/plugin/hook/SimplyPremiumCom.py b/pyload/plugin/hook/SimplyPremiumCom.py
new file mode 100644
index 000000000..f095cd7e6
--- /dev/null
+++ b/pyload/plugin/hook/SimplyPremiumCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class SimplyPremiumCom(MultiHook):
+ __name = "SimplyPremiumCom"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Simply-Premium.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def getHosters(self):
+ json_data = self.getURL("http://www.simply-premium.com/api/hosts.php", get={'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/plugin/hook/SimplydebridCom.py b/pyload/plugin/hook/SimplydebridCom.py
new file mode 100644
index 000000000..d831bf532
--- /dev/null
+++ b/pyload/plugin/hook/SimplydebridCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class SimplydebridCom(MultiHook):
+ __name = "SimplydebridCom"
+ __type = "hook"
+ __version = "0.04"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Simply-Debrid.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def getHosters(self):
+ html = self.getURL("http://simply-debrid.com/api.php", get={'list': 1})
+ return [x.strip() for x in html.rstrip(';').replace("\"", "").split(";")]
diff --git a/pyload/plugin/hook/SmoozedCom.py b/pyload/plugin/hook/SmoozedCom.py
new file mode 100644
index 000000000..caecb8bc2
--- /dev/null
+++ b/pyload/plugin/hook/SmoozedCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class SmoozedCom(MultiHook):
+ __name = "SmoozedCom"
+ __type = "hook"
+ __version = "0.03"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Smoozed.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("", "")]
+
+
+ def getHosters(self):
+ user, data = self.account.selectAccount()
+ return self.account.getAccountInfo(user)["hosters"]
diff --git a/pyload/plugin/hook/XFileSharingPro.py b/pyload/plugin/hook/XFileSharingPro.py
new file mode 100644
index 000000000..3c305c74b
--- /dev/null
+++ b/pyload/plugin/hook/XFileSharingPro.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hook import Hook
+
+
+class XFileSharingPro(Hook):
+ __name = "XFileSharingPro"
+ __type = "hook"
+ __version = "0.37"
+
+ __config = [("activated" , "bool", "Activated" , True ),
+ ("use_hoster_list" , "bool", "Load listed hosters only" , False),
+ ("use_crypter_list", "bool", "Load listed crypters only" , False),
+ ("use_builtin_list", "bool", "Load built-in plugin list" , True ),
+ ("hoster_list" , "str" , "Hoster list (comma separated)" , "" ),
+ ("crypter_list" , "str" , "Crypter list (comma separated)", "" )]
+
+ __description = """Load XFileSharingPro based hosters and crypter which don't need a own plugin to run"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ # event_list = ["pluginConfigChanged"]
+ regexp = {'hoster' : (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:embed-)?\w{12}(?:\W|$)',
+ r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:embed-)?\w+'),
+ 'crypter': (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:user|folder)s?/\w+',
+ r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:user|folder)s?/\w+')}
+
+ HOSTER_BUILTIN = [#WORKING HOSTERS:
+ "backin.net", "eyesfile.ca", "file4safe.com", "fileband.com", "filedwon.com", "fileparadox.in",
+ "filevice.com", "hostingbulk.com", "junkyvideo.com", "linestorage.com", "ravishare.com", "ryushare.com",
+ "salefiles.com", "sendmyway.com", "sharesix.com", "thefile.me", "verzend.be", "xvidstage.com",
+ # NOT TESTED:
+ "101shared.com", "4upfiles.com", "filemaze.ws", "filenuke.com", "linkzhost.com", "mightyupload.com",
+ "rockdizfile.com", "sharebeast.com", "sharerepo.com", "shareswift.com", "uploadbaz.com", "uploadc.com",
+ "vidbull.com", "worldbytez.com", "zalaa.com", "zomgupload.com",
+ # NOT WORKING:
+ "amonshare.com", "banicrazy.info", "boosterking.com", "host4desi.com", "laoupload.com", "rd-fs.com"]
+ CRYPTER_BUILTIN = ["junocloud.me", "rapidfileshare.net"]
+
+
+ # def pluginConfigChanged(self, plugin, name, value):
+ # self.loadPattern()
+
+
+ def activate(self):
+ self.loadPattern()
+
+
+ def loadPattern(self):
+ use_builtin_list = self.getConfig("use_builtin_list")
+
+ for type in ("hoster", "crypter"):
+ every_plugin = not self.getConfig('use_%s_list' % type)
+
+ if every_plugin:
+ self.logInfo(_("Handling any %s I can!") % type)
+ pattern = self.regexp[type][0]
+ else:
+ plugins = self.getConfig('%s_list' % type)
+ plugin_set = set(plugins.replace(' ', '').replace('\\', '').replace('|', ',').replace(';', ',').lower().split(','))
+
+ if use_builtin_list:
+ plugin_set |= set(x.lower() for x in getattr(self, "%s_BUILTIN" % type.upper()))
+
+ plugin_set -= set(('', u''))
+
+ if not plugin_set:
+ self.logInfo(_("No %s to handle") % type)
+ self._unload(type)
+ return
+
+ match_list = '|'.join(sorted(plugin_set))
+
+ len_match_list = len(plugin_set)
+ self.logInfo(_("Handling %d %s%s: %s") % (len_match_list,
+ type,
+ "" if len_match_list == 1 else "s",
+ match_list.replace('|', ', ')))
+
+ pattern = self.regexp[type][1] % match_list.replace('.', '\.')
+
+ dict = self.core.pluginManager.plugins[type]['XFileSharingPro']
+ dict['pattern'] = pattern
+ dict['re'] = re.compile(pattern)
+
+ self.logDebug("Loaded %s pattern: %s" % (type, pattern))
+
+
+ def _unload(self, type):
+ dict = self.core.pluginManager.plugins[type]['XFileSharingPro']
+ dict['pattern'] = r'^unmatchable$'
+ dict['re'] = re.compile(dict['pattern'])
+
+
+ def deactivate(self):
+ # self.unloadHoster("BasePlugin")
+ for type in ("hoster", "crypter"):
+ self._unload(type, "XFileSharingPro")
+
+
+ # def downloadFailed(self, pyfile):
+ # if pyfile.pluginname == "BasePlugin" \
+ # and pyfile.hasStatus("failed") \
+ # and not self.getConfig('use_hoster_list') \
+ # and self.unloadHoster("BasePlugin"):
+ # self.logDebug("Unloaded XFileSharingPro from BasePlugin")
+ # pyfile.setStatus("queued")
diff --git a/pyload/plugin/hook/ZeveraCom.py b/pyload/plugin/hook/ZeveraCom.py
new file mode 100644
index 000000000..e52ec8eed
--- /dev/null
+++ b/pyload/plugin/hook/ZeveraCom.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHook import MultiHook
+
+
+class ZeveraCom(MultiHook):
+ __name = "ZeveraCom"
+ __type = "hook"
+ __version = "0.05"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ),
+ ("revertfailed" , "bool" , "Revert to standard download if fails", True ),
+ ("reload" , "bool" , "Reload plugin list" , True ),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12 )]
+
+ __description = """Zevera.com hook plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ def getHosters(self):
+ html = self.account.api_response(pyreq.getHTTPRequest(timeout=120), cmd="gethosters")
+ return [x.strip() for x in html.split(",")]
diff --git a/pyload/plugin/hook/__init__.py b/pyload/plugin/hook/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/hook/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/hoster/AlldebridCom.py b/pyload/plugin/hoster/AlldebridCom.py
new file mode 100644
index 000000000..9f1f689cd
--- /dev/null
+++ b/pyload/plugin/hoster/AlldebridCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import parseFileSize
+
+
+class AlldebridCom(MultiHoster):
+ __name = "AlldebridCom"
+ __type = "hoster"
+ __version = "0.46"
+
+ __pattern = r'https?://(?:www\.|s\d+\.)?alldebrid\.com/dl/[\w^_]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Alldebrid.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Andy Voigt", "spamsales@online.de")]
+
+
+ def setup(self):
+ self.chunkLimit = 16
+
+
+ def handle_premium(self, pyfile):
+ password = self.getPassword()
+
+ data = json_loads(self.load("http://www.alldebrid.com/service.php",
+ get={'link': pyfile.url, 'json': "true", 'pw': password}))
+
+ self.logDebug("Json data", 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'])
+ self.link = data['link']
+
+ if self.getConfig('ssl'):
+ self.link = self.link.replace("http://", "https://")
+ else:
+ self.link = self.link.replace("https://", "http://")
diff --git a/pyload/plugin/hoster/AndroidfilehostCom.py b/pyload/plugin/hoster/AndroidfilehostCom.py
new file mode 100644
index 000000000..22cc71857
--- /dev/null
+++ b/pyload/plugin/hoster/AndroidfilehostCom.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# https://www.androidfilehost.com/?fid=95916177934518197
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class AndroidfilehostCom(SimpleHoster):
+ __name = "AndroidfilehostCom"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'https?://(?:www\.)?androidfilehost\.com/\?fid=\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Androidfilehost.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<br />(?P<N>.*?)</h1>'
+ SIZE_PATTERN = r'<h4>size</h4>\s*<p>(?P<S>[\d.,]+)(?P<U>[\w^_]+)</p>'
+ HASHSUM_PATTERN = r'<h4>(?P<T>.*?)</h4>\s*<p><code>(?P<H>.*?)</code></p>'
+
+ OFFLINE_PATTERN = r'404 not found'
+
+ WAIT_PATTERN = r'users must wait <strong>(\d+) secs'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ wait = re.search(self.WAIT_PATTERN, self.html)
+ self.logDebug("Waiting time: %s seconds" % wait.group(1))
+
+ fid = re.search(r'id="fid" value="(\d+)" />', self.html).group(1)
+ self.logDebug("fid: %s" % fid)
+
+ html = self.load("https://www.androidfilehost.com/libs/otf/mirrors.otf.php",
+ post={'submit': 'submit',
+ 'action': 'getdownloadmirrors',
+ 'fid' : fid},
+ decode=True)
+
+ self.link = re.findall('"url":"(.*?)"', html)[0].replace("\\", "")
+ mirror_host = self.link.split("/")[2]
+
+ self.logDebug("Mirror Host: %s" % mirror_host)
+
+ html = self.load("https://www.androidfilehost.com/libs/otf/stats.otf.php",
+ get={'fid' : fid,
+ 'w' : 'download',
+ 'mirror': mirror_host},
+ decode=True)
diff --git a/pyload/plugin/hoster/BasketbuildCom.py b/pyload/plugin/hoster/BasketbuildCom.py
new file mode 100644
index 000000000..2e94eefe2
--- /dev/null
+++ b/pyload/plugin/hoster/BasketbuildCom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# https://s.basketbuild.com/filedl/devs?dev=pacman&dl=pacman/falcon/RC-3/pac_falcon-RC-3-20141103.zip
+# https://s.basketbuild.com/filedl/gapps?dl=gapps-gb-20110828-signed.zip
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class BasketbuildCom(SimpleHoster):
+ __name = "BasketbuildCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?(?:\w\.)?basketbuild\.com/filedl/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """basketbuild.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'File Name:</strong> (?P<N>.+?)<br/>'
+ SIZE_PATTERN = r'File Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'404 - Page Not Found'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ try:
+ link1 = re.search(r'href="(.+dlgate/.+)"', self.html).group(1)
+ self.html = self.load(link1)
+
+ except AttributeError:
+ self.error(_("Hop #1 not found"))
+
+ else:
+ self.logDebug("Next hop: %s" % link1)
+
+ try:
+ wait = re.search(r'var sec = (\d+)', self.html).group(1)
+ self.logDebug("Wait %s seconds" % wait)
+ self.wait(wait)
+
+ except AttributeError:
+ self.logDebug("No wait time found")
+
+ try:
+ self.link = re.search(r'id="dlLink">\s*<a href="(.+?)"', self.html).group(1)
+
+ except AttributeError:
+ self.error(_("DL-Link not found"))
diff --git a/pyload/plugin/hoster/BayfilesCom.py b/pyload/plugin/hoster/BayfilesCom.py
new file mode 100644
index 000000000..b14e28dcb
--- /dev/null
+++ b/pyload/plugin/hoster/BayfilesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class BayfilesCom(DeadHoster):
+ __name = "BayfilesCom"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>\w+/\w+/[^/]+)'
+ __config = []
+
+ __description = """Bayfiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/BezvadataCz.py b/pyload/plugin/hoster/BezvadataCz.py
new file mode 100644
index 000000000..33e641635
--- /dev/null
+++ b/pyload/plugin/hoster/BezvadataCz.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class BezvadataCz(SimpleHoster):
+ __name = "BezvadataCz"
+ __type = "hoster"
+ __version = "0.26"
+
+ __pattern = r'http://(?:www\.)?bezvadata\.cz/stahnout/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """BezvaData.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>'
+ SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>'
+ OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ # download button
+ m = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html)
+ if m is None:
+ self.error(_("Page 1 URL not found"))
+ url = "http://bezvadata.cz%s" % m.group(1)
+
+ # captcha form
+ self.html = self.load(url)
+ self.checkErrors()
+ for _i in xrange(5):
+ action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm')
+ if not inputs:
+ self.error(_("FreeForm"))
+
+ m = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html)
+ if m is None:
+ self.error(_("Wrong captcha image"))
+
+ # 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.error(_("Page 2 URL not found"))
+ 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))) if m else 120
+ self.wait(wait_time, False)
+
+ self.link = 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()
+
+ self.info.pop('error', None)
+
+
+ def loadcaptcha(self, data, *args, **kwargs):
+ return data.decode('base64')
diff --git a/pyload/plugin/hoster/BillionuploadsCom.py b/pyload/plugin/hoster/BillionuploadsCom.py
new file mode 100644
index 000000000..bed3a1d54
--- /dev/null
+++ b/pyload/plugin/hoster/BillionuploadsCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class BillionuploadsCom(DeadHoster):
+ __name = "BillionuploadsCom"
+ __type = "hoster"
+ __version = "0.06"
+
+ __pattern = r'http://(?:www\.)?billionuploads\.com/\w{12}'
+
+ __description = """Billionuploads.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/BitshareCom.py b/pyload/plugin/hoster/BitshareCom.py
new file mode 100644
index 000000000..0d26c2d53
--- /dev/null
+++ b/pyload/plugin/hoster/BitshareCom.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class BitshareCom(SimpleHoster):
+ __name = "BitshareCom"
+ __type = "hoster"
+ __version = "0.53"
+
+ __pattern = r'http://(?:www\.)?bitshare\.com/(files/)?(?(1)|\?f=)(?P<ID>\w+)(?(1)/(?P<NAME>.+?)\.html)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Bitshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Paul King", ""),
+ ("fragonib", "fragonib[AT]yahoo[DOT]es")]
+
+
+ COOKIES = [("bitshare.com", "language_selection", "EN")]
+
+ INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</h1>'
+ OFFLINE_PATTERN = r'[Ff]ile (not available|was deleted|was not found)'
+
+ AJAXID_PATTERN = r'var ajaxdl = "(.*?)";'
+ TRAFFIC_USED_UP = r'Your Traffic is used up for today'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.chunkLimit = 1
+
+
+ def process(self, pyfile):
+ if self.premium:
+ self.account.relogin(self.user)
+
+ # 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.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.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
+ self.link = self.getDownloadUrl()
+
+ if self.checkDownload({"error": ">Error occured<"}):
+ 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, just_header=True)
+ if 'location' in header:
+ return header['location']
+
+ # Get download info
+ self.logDebug("Getting download info")
+ res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request": "generateID", "ajaxid": self.ajaxid})
+
+ self.handleErrors(res, ':')
+
+ parts = res.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")
+ recaptcha = ReCaptcha(self)
+
+ # Try up to 3 times
+ for _i in xrange(3):
+ response, challenge = recaptcha.challenge()
+ res = 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" : response})
+ if self.handleCaptchaErrors(res):
+ break
+
+ # Get download URL
+ self.logDebug("Getting download url")
+ res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request": "getDownloadURL", "ajaxid": self.ajaxid})
+
+ self.handleErrors(res, '#')
+
+ url = res.split("#")[-1]
+
+ return url
+
+
+ def handleErrors(self, res, separator):
+ self.logDebug("Checking response [%s]" % res)
+ if "ERROR:Session timed out" in res:
+ self.retry()
+ elif "ERROR" in res:
+ msg = res.split(separator)[-1]
+ self.fail(msg)
+
+
+ def handleCaptchaErrors(self, res):
+ self.logDebug("Result of captcha resolving [%s]" % res)
+ if "SUCCESS" in res:
+ self.correctCaptcha()
+ return True
+ elif "ERROR:SESSION ERROR" in res:
+ self.retry()
+
+ self.invalidCaptcha()
diff --git a/pyload/plugin/hoster/BoltsharingCom.py b/pyload/plugin/hoster/BoltsharingCom.py
new file mode 100644
index 000000000..10716e695
--- /dev/null
+++ b/pyload/plugin/hoster/BoltsharingCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class BoltsharingCom(DeadHoster):
+ __name = "BoltsharingCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?boltsharing\.com/\w{12}'
+ __config = []
+
+ __description = """Boltsharing.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/CatShareNet.py b/pyload/plugin/hoster/CatShareNet.py
new file mode 100644
index 000000000..124d22591
--- /dev/null
+++ b/pyload/plugin/hoster/CatShareNet.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class CatShareNet(SimpleHoster):
+ __name = "CatShareNet"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'http://(?:www\.)?catshare\.net/\w{16}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """CatShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com"),
+ ("prOq", ""),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ TEXT_ENCODING = True
+
+ INFO_PATTERN = r'<title>(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<'
+ OFFLINE_PATTERN = r'<div class="alert alert-error"'
+
+ IP_BLOCKED_PATTERN = ur'>Nasz serwis wykrył ÅŒe Twój adres IP nie pochodzi z Polski.<'
+ WAIT_PATTERN = r'var\scount\s=\s(\d+);'
+
+ LINK_FREE_PATTERN = r'<form action="(.+?)" method="GET">'
+ LINK_PREMIUM_PATTERN = r'<form action="(.+?)" method="GET">'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+
+ def checkErrors(self):
+ m = re.search(self.IP_BLOCKED_PATTERN, self.html)
+ if m:
+ self.fail(_("Only connections from Polish IP address are allowed"))
+
+ return super(CatShareNet, self).checkErrors()
+
+
+ def handle_free(self, pyfile):
+ recaptcha = ReCaptcha(self)
+
+ response, challenge = recaptcha.challenge()
+ self.html = self.load(pyfile.url,
+ post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/CloudzerNet.py b/pyload/plugin/hoster/CloudzerNet.py
new file mode 100644
index 000000000..c1b3beae8
--- /dev/null
+++ b/pyload/plugin/hoster/CloudzerNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class CloudzerNet(DeadHoster):
+ __name = "CloudzerNet"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?(cloudzer\.net/file/|clz\.to/(file/)?)\w+'
+ __config = []
+
+ __description = """Cloudzer.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("gs", "I-_-I-_-I@web.de"),
+ ("z00nx", "z00nx0@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/CloudzillaTo.py b/pyload/plugin/hoster/CloudzillaTo.py
new file mode 100644
index 000000000..7b1faaed2
--- /dev/null
+++ b/pyload/plugin/hoster/CloudzillaTo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class CloudzillaTo(SimpleHoster):
+ __name = "CloudzillaTo"
+ __type = "hoster"
+ __version = "0.06"
+
+ __pattern = r'http://(?:www\.)?cloudzilla\.to/share/file/(?P<ID>[\w^_]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Cloudzilla.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'title="(?P<N>.+?)">\1</span> <span class="size">\((?P<S>[\d.]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>File not found...<'
+
+ PASSWORD_PATTERN = r'<div id="pwd_protected">'
+
+
+ def checkErrors(self):
+ m = re.search(self.PASSWORD_PATTERN, self.html)
+ if m:
+ self.html = self.load(self.pyfile.url, get={'key': self.getPassword()})
+
+ if re.search(self.PASSWORD_PATTERN, self.html):
+ self.retry(reason="Wrong password")
+
+
+ def handle_free(self, pyfile):
+ self.html = self.load("http://www.cloudzilla.to/generateticket/",
+ post={'file_id': self.info['pattern']['ID'], 'key': self.getPassword()})
+
+ ticket = dict(re.findall(r'<(.+?)>([^<>]+?)</', self.html))
+
+ self.logDebug(ticket)
+
+ if 'error' in ticket:
+ if "File is password protected" in ticket['error']:
+ self.retry(reason="Wrong password")
+ else:
+ self.fail(ticket['error'])
+
+ if 'wait' in ticket:
+ self.wait(ticket['wait'], int(ticket['wait']) > 5)
+
+ self.link = "http://%(server)s/download/%(file_id)s/%(ticket_id)s" % {'server' : ticket['server'],
+ 'file_id' : self.info['pattern']['ID'],
+ 'ticket_id': ticket['ticket_id']}
+
+
+ def handle_premium(self, pyfile):
+ return self.handle_free(pyfile)
diff --git a/pyload/plugin/hoster/CramitIn.py b/pyload/plugin/hoster/CramitIn.py
new file mode 100644
index 000000000..9d8d4960d
--- /dev/null
+++ b/pyload/plugin/hoster/CramitIn.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class CramitIn(XFSHoster):
+ __name = "CramitIn"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?cramit\.in/\w{12}'
+
+ __description = """Cramit.in hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<span class=t2>\s*(?P<N>.*?)</span>.*?<small>\s*\((?P<S>.*?)\)'
+
+ LINK_PATTERN = r'href="(http://cramit\.in/file_download/.*?)"'
diff --git a/pyload/plugin/hoster/CrockoCom.py b/pyload/plugin/hoster/CrockoCom.py
new file mode 100644
index 000000000..aaced0cdb
--- /dev/null
+++ b/pyload/plugin/hoster/CrockoCom.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class CrockoCom(SimpleHoster):
+ __name = "CrockoCom"
+ __type = "hoster"
+ __version = "0.19"
+
+ __pattern = r'http://(?:www\.)?(crocko|easy-share)\.com/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Crocko hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<span class="fz24">Download:\s*<strong>(?P<N>.*)'
+ 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_PATTERN = r"u='(/file_contents/captcha/\w+)';\s*w='(\d+)';"
+
+ FORM_PATTERN = r'<form method="post" action="(.+?)">(.*?)</form>'
+ FORM_INPUT_PATTERN = r'<input[^>]* name="?([^" ]+)"? value="?([^" ]+)"?.*?>'
+
+ NAME_REPLACEMENTS = [(r'<.*?>', '')]
+
+
+ def handle_free(self, pyfile):
+ if "You need Premium membership to download this file." in self.html:
+ self.fail(_("You need Premium membership to download this file"))
+
+ for _i in xrange(5):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ url, wait_time = 'http://crocko.com' + m.group(1), int(m.group(2))
+ self.wait(wait_time)
+ self.html = self.load(url)
+ else:
+ break
+
+ m = re.search(self.FORM_PATTERN, self.html, re.S)
+ if m is None:
+ self.error(_("FORM_PATTERN not found"))
+
+ action, form = m.groups()
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
+ self.download(action, post=inputs)
+
+ if self.checkDownload({"captcha": recaptcha.KEY_AJAX_PATTERN}):
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail(_("No valid captcha solution received"))
diff --git a/pyload/plugin/hoster/CyberlockerCh.py b/pyload/plugin/hoster/CyberlockerCh.py
new file mode 100644
index 000000000..b56736a8e
--- /dev/null
+++ b/pyload/plugin/hoster/CyberlockerCh.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class CyberlockerCh(DeadHoster):
+ __name = "CyberlockerCh"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?cyberlocker\.ch/\w+'
+ __config = []
+
+ __description = """Cyberlocker.ch hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/CzshareCom.py b/pyload/plugin/hoster/CzshareCom.py
new file mode 100644
index 000000000..85ed238f0
--- /dev/null
+++ b/pyload/plugin/hoster/CzshareCom.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://czshare.com/5278880/random.bin
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import parseFileSize
+
+
+class CzshareCom(SimpleHoster):
+ __name = "CzshareCom"
+ __type = "hoster"
+ __version = "0.99"
+
+ __pattern = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download\.php\?).+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """CZshare.com hoster plugin, now Sdilej.cz"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<div class="tab" id="parameters">\s*<p>\s*Cel. n.zev: <a href=.*?>(?P<N>[^<]+)</a>'
+ SIZE_PATTERN = r'<div class="tab" id="category">(?:\s*<p>[^\n]*</p>)*\s*Velikost:\s*(?P<S>[\d .,]+)(?P<U>[\w^_]+)\s*</div>'
+ OFFLINE_PATTERN = r'<div class="header clearfix">\s*<h2 class="red">'
+
+ SIZE_REPLACEMENTS = [(' ', '')]
+ URL_REPLACEMENTS = [(r'http://[^/]*/download.php\?.*?id=(\w+).*', r'http://sdilej.cz/\1/x/')]
+
+ 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>([\d .,]+)(\w+)</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, 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(e)
+
+ return True
+
+
+ def handle_premium(self, pyfile):
+ # parse download link
+ try:
+ form = re.search(self.PREMIUM_FORM_PATTERN, self.html, re.S).group(1)
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ except Exception, e:
+ self.logError(e)
+ self.resetAccount()
+
+ # download the file, destination is determined by pyLoad
+ self.download("http://sdilej.cz/profi_down.php", post=inputs, disposition=True)
+
+
+ def handle_free(self, pyfile):
+ # get free url
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m is None:
+ self.error(_("FREE_URL_PATTERN not found"))
+
+ 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, 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.S).group(1)
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ pyfile.size = int(inputs['size'])
+
+ except Exception, e:
+ self.logError(e)
+ self.error(_("Form"))
+
+ # get and decrypt captcha
+ captcha_url = 'http://sdilej.cz/captcha.php'
+ for _i in xrange(5):
+ inputs['captchastring2'] = self.decryptCaptcha(captcha_url)
+ self.html = self.load(parsed_url, 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.error(_("Download URL not found"))
+
+ self.link = "http://%s/download.php?%s" % (m.group(1), m.group(2))
+
+ self.wait()
+
+
+ def checkFile(self, rules={}):
+ # check download
+ check = self.checkDownload({
+ "temp offline" : 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" : "<li>ZadanÜ ověřovací kód nesouhlasí!</li>"
+ })
+
+ if check == "temp offline":
+ self.fail(_("File not available - try later"))
+
+ elif check == "credit":
+ self.resetAccount()
+
+ elif check == "multi-dl":
+ self.longWait(5 * 60, 12)
+
+ elif check == "captcha":
+ self.invalidCaptcha()
+ self.retry()
+
+ return super(CzshareCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/DailymotionCom.py b/pyload/plugin/hoster/DailymotionCom.py
new file mode 100644
index 000000000..3fdac761c
--- /dev/null
+++ b/pyload/plugin/hoster/DailymotionCom.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.datatype.File import statusMap
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+
+
+def getInfo(urls):
+ result = []
+ regex = re.compile(DailymotionCom.__pattern)
+ apiurl = "https://api.dailymotion.com/video/%s"
+ request = {"fields": "access_error,status,title"}
+
+ for url in urls:
+ id = regex.match(url).group('ID')
+ html = getURL(apiurl % id, get=request)
+ info = json_loads(html)
+
+ name = info['title'] + ".mp4" if "title" in info else 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.20"
+
+ __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"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ 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 [item for item in enumerate(streams)][::-1]:
+ 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()
+
+ self.download(self.getLink(streams, quality))
diff --git a/pyload/plugin/hoster/DataHu.py b/pyload/plugin/hoster/DataHu.py
new file mode 100644
index 000000000..16b3c2737
--- /dev/null
+++ b/pyload/plugin/hoster/DataHu.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://data.hu/get/6381232/random.bin
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DataHu(SimpleHoster):
+ __name = "DataHu"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?data\.hu/get/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Data.hu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("crash", ""),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ INFO_PATTERN = ur'<title>(?P<N>.*) \((?P<S>[^)]+)\) let\xf6lt\xe9se</title>'
+ OFFLINE_PATTERN = ur'Az adott f\xe1jl nem l\xe9tezik'
+ LINK_FREE_PATTERN = r'<div class="download_box_button"><a href="(.+?)">'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
diff --git a/pyload/plugin/hoster/DataportCz.py b/pyload/plugin/hoster/DataportCz.py
new file mode 100644
index 000000000..15ae7d0d6
--- /dev/null
+++ b/pyload/plugin/hoster/DataportCz.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DataportCz(SimpleHoster):
+ __name = "DataportCz"
+ __type = "hoster"
+ __version = "0.41"
+
+ __pattern = r'http://(?:www\.)?dataport\.cz/file/(.+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Dataport.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<span itemprop="name">(?P<N>[^<]+)</span>'
+ SIZE_PATTERN = r'<td class="fil">Velikost</td>\s*<td>(?P<S>[^<]+)</td>'
+ OFFLINE_PATTERN = r'<h2>Soubor nebyl nalezen</h2>'
+
+ CAPTCHA_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 handle_free(self, pyfile):
+ captchas = {"1": "jkeG", "2": "hMJQ", "3": "vmEK", "4": "ePQM", "5": "blBd"}
+
+ for _i in xrange(60):
+ action, inputs = self.parseHtmlForm('free_download_form')
+ self.logDebug(action, inputs)
+ if not action or not inputs:
+ self.error(_("free_download_form"))
+
+ if "captchaId" in inputs and inputs['captchaId'] in captchas:
+ inputs['captchaCode'] = captchas[inputs['captchaId']]
+ else:
+ self.error(_("captcha"))
+
+ 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.error(_("invalid captcha"))
+
+ elif check == "slot":
+ self.logDebug("No free slots - wait 60s and retry")
+ self.wait(60, False)
+ self.html = self.load(pyfile.url, decode=True)
+ continue
+
+ else:
+ break
diff --git a/pyload/plugin/hoster/DateiTo.py b/pyload/plugin/hoster/DateiTo.py
new file mode 100644
index 000000000..eaca8d6db
--- /dev/null
+++ b/pyload/plugin/hoster/DateiTo.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DateiTo(SimpleHoster):
+ __name = "DateiTo"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Datei.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'Dateiname:</td>\s*<td colspan="2"><strong>(?P<N>.*?)</'
+ 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... <'
+
+ WAIT_PATTERN = r'countdown\({seconds: (\d+)'
+ MULTIDL_PATTERN = r'>Du lÀdst bereits eine Datei herunter<'
+
+ DATA_PATTERN = r'url: "(.*?)", data: "(.*?)",'
+
+
+ def handle_free(self, pyfile):
+ url = 'http://datei.to/ajax/download.php'
+ data = {'P': 'I', 'ID': self.info['pattern']['ID']}
+ recaptcha = ReCaptcha(self)
+
+ for _i 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.error(_("data"))
+ url = 'http://datei.to/' + m.group(1)
+ data = dict(x.split('=') for x in m.group(2).split('&'))
+
+ if url.endswith('recaptcha.php'):
+ data['recaptcha_response_field'], data['recaptcha_challenge_field'] = recaptcha.challenge()
+ else:
+ self.fail(_("Too bad..."))
+
+ self.link = self.html
+
+
+ def checkErrors(self):
+ m = re.search(self.MULTIDL_PATTERN, self.html)
+ if m:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ wait_time = int(m.group(1)) if m else 30
+
+ errmsg = self.info['error'] = _("Parallel downloads")
+ self.retry(wait_time=wait_time, reason=errmsg)
+
+ self.info.pop('error', None)
+
+
+ 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, False)
diff --git a/pyload/plugin/hoster/DdlstorageCom.py b/pyload/plugin/hoster/DdlstorageCom.py
new file mode 100644
index 000000000..53c0df7cb
--- /dev/null
+++ b/pyload/plugin/hoster/DdlstorageCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class DdlstorageCom(DeadHoster):
+ __name = "DdlstorageCom"
+ __type = "hoster"
+ __version = "1.02"
+
+ __pattern = r'https?://(?:www\.)?ddlstorage\.com/\w+'
+ __config = []
+
+ __description = """DDLStorage.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/DebridItaliaCom.py b/pyload/plugin/hoster/DebridItaliaCom.py
new file mode 100644
index 000000000..56bb355d5
--- /dev/null
+++ b/pyload/plugin/hoster/DebridItaliaCom.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class DebridItaliaCom(MultiHoster):
+ __name = "DebridItaliaCom"
+ __type = "hoster"
+ __version = "0.17"
+
+ __pattern = r'https?://(?:www\.|s\d+\.)?debriditalia\.com/dl/\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Debriditalia.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [("https://", "http://")]
+
+
+ def handle_premium(self, pyfile):
+ self.html = self.load("http://www.debriditalia.com/api.php",
+ get={'generate': "on", 'link': pyfile.url, 'p': self.getPassword()})
+
+ if "ERROR:" not in self.html:
+ self.link = self.html.strip()
+ else:
+ self.info['error'] = re.search(r'ERROR:(.*)', self.html).group(1).strip()
+
+ self.html = self.load("http://debriditalia.com/linkgen2.php",
+ post={'xjxfun' : "convertiLink",
+ 'xjxargs[]': "S<![CDATA[%s]]>" % pyfile.url,
+ 'xjxargs[]': "S%s" % self.getPassword()})
+ try:
+ self.link = re.search(r'<a href="(.+?)"', self.html).group(1)
+ except AttributeError:
+ pass
diff --git a/pyload/plugin/hoster/DepositfilesCom.py b/pyload/plugin/hoster/DepositfilesCom.py
new file mode 100644
index 000000000..49f9d4384
--- /dev/null
+++ b/pyload/plugin/hoster/DepositfilesCom.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DepositfilesCom(SimpleHoster):
+ __name = "DepositfilesCom"
+ __type = "hoster"
+ __version = "0.55"
+
+ __pattern = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Depositfiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<script type="text/javascript">eval\( unescape\(\'(?P<N>.*?)\''
+ SIZE_PATTERN = r': <b>(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</b>'
+ OFFLINE_PATTERN = r'<span class="html_download_api-not_exists"></span>'
+
+ NAME_REPLACEMENTS = [(r'\%u([0-9A-Fa-f]{4})', lambda m: unichr(int(m.group(1), 16))),
+ (r'.*<b title="(?P<N>.+?)".*', "\g<N>")]
+ URL_REPLACEMENTS = [(__pattern + ".*", "https://dfiles.eu/files/\g<ID>")]
+
+ COOKIES = [("dfiles.eu", "lang_current", "en")]
+
+ WAIT_PATTERN = r'(?:download_waiter_remain">|html_download_api-limit_interval">|>Please wait|>Try in).+'
+ ERROR_PATTER = r'File is checked, please try again in a minute'
+
+ LINK_FREE_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"'
+ LINK_PREMIUM_PATTERN = r'class="repeat"><a href="(.+?)"'
+ LINK_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"'
+
+
+ def handle_free(self, pyfile):
+ self.html = self.load(pyfile.url, post={'gateway_result': "1"})
+
+ self.checkErrors()
+
+ 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'])
+
+ self.checkErrors()
+
+ recaptcha = ReCaptcha(self)
+ captcha_key = recaptcha.detect_key()
+ if captcha_key is None:
+ return
+
+ self.html = self.load("https://dfiles.eu/get_file.php", get=params)
+
+ if '<input type=button value="Continue" onclick="check_recaptcha' in self.html:
+ params['response'], params['challenge'] = recaptcha.challenge(captcha_key)
+ self.html = self.load("https://dfiles.eu/get_file.php", get=params)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = urllib.unquote(m.group(1))
+
+
+ def handle_premium(self, pyfile):
+ 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.LINK_PREMIUM_PATTERN, self.html)
+ mirror = re.search(self.LINK_MIRROR_PATTERN, self.html)
+
+ if link:
+ self.link = link.group(1)
+
+ elif mirror:
+ self.link = mirror.group(1)
diff --git a/pyload/plugin/hoster/DevhostSt.py b/pyload/plugin/hoster/DevhostSt.py
new file mode 100644
index 000000000..f35530523
--- /dev/null
+++ b/pyload/plugin/hoster/DevhostSt.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://d-h.st/mM8
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class DevhostSt(SimpleHoster):
+ __name = "DevhostSt"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?d-h\.st/(?!users/)\w{3}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """d-h.st hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<span title="(?P<N>.*?)"'
+ SIZE_PATTERN = r'</span> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<br'
+ HASHSUM_PATTERN = r'>(?P<T>.*?) Sum</span>: &nbsp;(?P<H>.*?)<br'
+
+ OFFLINE_PATTERN = r'>File Not Found'
+ LINK_FREE_PATTERN = r'var product_download_url= \'(.+?)\''
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
diff --git a/pyload/plugin/hoster/DlFreeFr.py b/pyload/plugin/hoster/DlFreeFr.py
new file mode 100644
index 000000000..2c4889d67
--- /dev/null
+++ b/pyload/plugin/hoster/DlFreeFr.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.network.Browser import Browser
+from pyload.network.CookieJar import CookieJar
+from pyload.plugin.captcha.AdYouLike import AdYouLike
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns
+from pyload.utils import json_loads
+
+
+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 DlFreeFr(SimpleHoster):
+ __name = "DlFreeFr"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?dl\.free\.fr/(\w+|getfile\.pl\?file=/\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Dl.free.fr hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("the-razer", "daniel_ AT gmx DOT net"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("Toilal", "toilal.dev@gmail.com")]
+
+
+ NAME_PATTERN = r'Fichier:</td>\s*<td.*?>(?P<N>[^>]*)</td>'
+ SIZE_PATTERN = r'Taille:</td>\s*<td.*?>(?P<S>[\d.,]+\w)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.resumeDownload = True
+ self.multiDL = 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):
+ pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
+ valid_url = pyfile.url
+ headers = self.load(valid_url, just_header=True)
+
+ 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.handle_free(pyfile)
+ else:
+ # Direct access to requested file for users using free.fr as Internet Service Provider.
+ self.link = valid_url
+
+ elif headers.get('code') == 404:
+ self.offline()
+
+ else:
+ self.fail(_("Invalid return code: ") + str(headers.get('code')))
+
+
+ def handle_free(self, pyfile):
+ action, inputs = self.parseHtmlForm('action="getfile.pl"')
+
+ adyoulike = AdYouLike(self)
+ response, challenge = adyoulike.challenge()
+ inputs.update(response)
+
+ 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"))
+
+ self.link = headers.get("location")
+
+ self.req.setCookieJar(cj)
+ 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
diff --git a/pyload/plugin/hoster/DodanePl.py b/pyload/plugin/hoster/DodanePl.py
new file mode 100644
index 000000000..8edbb64c0
--- /dev/null
+++ b/pyload/plugin/hoster/DodanePl.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class DodanePl(DeadHoster):
+ __name = "DodanePl"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?dodane\.pl/file/\d+'
+ __config = []
+
+ __description = """Dodane.pl hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
diff --git a/pyload/plugin/hoster/DuploadOrg.py b/pyload/plugin/hoster/DuploadOrg.py
new file mode 100644
index 000000000..decd1a7ff
--- /dev/null
+++ b/pyload/plugin/hoster/DuploadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class DuploadOrg(DeadHoster):
+ __name = "DuploadOrg"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?dupload\.org/\w{12}'
+ __config = []
+
+ __description = """Dupload.grg hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/EasybytezCom.py b/pyload/plugin/hoster/EasybytezCom.py
new file mode 100644
index 000000000..8dcb04b98
--- /dev/null
+++ b/pyload/plugin/hoster/EasybytezCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class EasybytezCom(XFSHoster):
+ __name = "EasybytezCom"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'http://(?:www\.)?easybytez\.com/\w{12}'
+
+ __description = """Easybytez.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ OFFLINE_PATTERN = r'>File not available'
+
+ LINK_PATTERN = r'(http://(\w+\.(easybytez|easyload|ezbytez|zingload)\.(com|to)|\d+\.\d+\.\d+\.\d+)/files/\d+/\w+/.+?)["\'<]'
diff --git a/pyload/plugin/hoster/EdiskCz.py b/pyload/plugin/hoster/EdiskCz.py
new file mode 100644
index 000000000..7f5f76b73
--- /dev/null
+++ b/pyload/plugin/hoster/EdiskCz.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class EdiskCz(SimpleHoster):
+ __name = "EdiskCz"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'http://(?:www\.)?edisk\.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Edisk.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<span class="fl" title="(?P<N>.+?)">\s*.*?\((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</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_FREE_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.error(_("ACTION_PATTERN not found"))
+ 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_FREE_PATTERN, url):
+ self.fail(_("Unexpected server response"))
+
+ self.link = url
diff --git a/pyload/plugin/hoster/EgoFilesCom.py b/pyload/plugin/hoster/EgoFilesCom.py
new file mode 100644
index 000000000..f87c96dd1
--- /dev/null
+++ b/pyload/plugin/hoster/EgoFilesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class EgoFilesCom(DeadHoster):
+ __name = "EgoFilesCom"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'https?://(?:www\.)?egofiles\.com/\w+'
+ __config = []
+
+ __description = """Egofiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/EnteruploadCom.py b/pyload/plugin/hoster/EnteruploadCom.py
new file mode 100644
index 000000000..e64c284f6
--- /dev/null
+++ b/pyload/plugin/hoster/EnteruploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class EnteruploadCom(DeadHoster):
+ __name = "EnteruploadCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?enterupload\.com/\w+'
+ __config = []
+
+ __description = """EnterUpload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/EpicShareNet.py b/pyload/plugin/hoster/EpicShareNet.py
new file mode 100644
index 000000000..05a1aca40
--- /dev/null
+++ b/pyload/plugin/hoster/EpicShareNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class EpicShareNet(DeadHoster):
+ __name = "EpicShareNet"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?epicshare\.net/\w{12}'
+ __config = []
+
+ __description = """EpicShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/EuroshareEu.py b/pyload/plugin/hoster/EuroshareEu.py
new file mode 100644
index 000000000..09fd1bf5b
--- /dev/null
+++ b/pyload/plugin/hoster/EuroshareEu.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class EuroshareEu(SimpleHoster):
+ __name = "EuroshareEu"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?euroshare\.(eu|sk|cz|hu|pl)/file/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Euroshare.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ 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!'
+
+ LINK_FREE_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/"'
+
+ URL_REPLACEMENTS = [(r"(http://[^/]*\.)(sk|cz|hu|pl)/", r"\1eu/")]
+
+
+ def handle_premium(self, pyfile):
+ if self.ERR_NOT_LOGGED_IN_PATTERN in self.html:
+ self.account.relogin(self.user)
+ self.retry(reason=_("User not logged in"))
+
+ self.link = 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 handle_free(self, pyfile):
+ if re.search(self.ERR_PARDL_PATTERN, self.html):
+ self.longWait(5 * 60, 12)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = "http://euroshare.eu%s" % m.group(1)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({"multi-dl": re.compile(self.ERR_PARDL_PATTERN)})
+ self.longWait(5 * 60, 12)
+
+ return super(EuroshareEu, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/ExashareCom.py b/pyload/plugin/hoster/ExashareCom.py
new file mode 100644
index 000000000..6036da66d
--- /dev/null
+++ b/pyload/plugin/hoster/ExashareCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class ExashareCom(XFSHoster):
+ __name = "ExashareCom"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?exashare\.com/\w{12}'
+
+ __description = """Exashare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'>(?P<NAME>.+?)<small>\( (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ LINK_FREE_PATTERN = r'file: "(.+?)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+ else:
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/ExtabitCom.py b/pyload/plugin/hoster/ExtabitCom.py
new file mode 100644
index 000000000..6fd65842d
--- /dev/null
+++ b/pyload/plugin/hoster/ExtabitCom.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+
+
+class ExtabitCom(SimpleHoster):
+ __name = "ExtabitCom"
+ __type = "hoster"
+ __version = "0.65"
+
+ __pattern = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Extabit.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<th>File:</th>\s*<td class="col-fileinfo">\s*<div title="(?P<N>.+?)">'
+ 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_FREE_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/\w+/.*?)[\'"]'
+
+
+ def handle_free(self, pyfile):
+ 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.info['pattern']['ID']
+
+ m = re.search(r'recaptcha/api/challenge\?k=(\w+)', self.html)
+ if m:
+ recaptcha = ReCaptcha(self)
+ captcha_key = m.group(1)
+
+ for _i in xrange(5):
+ get_data = {"type": "recaptcha"}
+ get_data['capture'], get_data['challenge'] = recaptcha.challenge(captcha_key)
+ res = json_loads(self.load("http://extabit.com/file/%s/" % fileID, get=get_data))
+ if "ok" in res:
+ self.correctCaptcha()
+ break
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail(_("Invalid captcha"))
+ else:
+ self.error(_("Captcha"))
+
+ if not "href" in res:
+ self.error(_("Bad JSON response"))
+
+ self.html = self.load("http://extabit.com/file/%s%s" % (fileID, res['href']))
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/FastixRu.py b/pyload/plugin/hoster/FastixRu.py
new file mode 100644
index 000000000..0f79ac44c
--- /dev/null
+++ b/pyload/plugin/hoster/FastixRu.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class FastixRu(MultiHoster):
+ __name = "FastixRu"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?fastix\.(ru|it)/file/\w{24}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Fastix multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Massimo Rosamilia", "max@spiritix.eu")]
+
+
+ def setup(self):
+ self.chunkLimit = 3
+
+
+ def handle_premium(self, pyfile):
+ api_key = self.account.getAccountData(self.user)
+ api_key = api_key['api']
+
+ self.html = self.load("http://fastix.ru/api_v2/",
+ get={'apikey': api_key, 'sub': "getdirectlink", 'link': pyfile.url})
+
+ data = json_loads(self.html)
+
+ self.logDebug("Json data", data)
+
+ if "error\":true" in self.html:
+ self.offline()
+ else:
+ self.link = data['downloadlink']
diff --git a/pyload/plugin/hoster/FastshareCz.py b/pyload/plugin/hoster/FastshareCz.py
new file mode 100644
index 000000000..cd054b61c
--- /dev/null
+++ b/pyload/plugin/hoster/FastshareCz.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FastshareCz(SimpleHoster):
+ __name = "FastshareCz"
+ __type = "hoster"
+ __version = "0.29"
+
+ __pattern = r'http://(?:www\.)?fastshare\.cz/\d+/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FastShare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ URL_REPLACEMENTS = [("#.*", "")]
+
+ COOKIES = [("fastshare.cz", "lang", "en")]
+
+ NAME_PATTERN = r'<h3 class="section_title">(?P<N>.+?)<'
+ SIZE_PATTERN = r'>Size\s*:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)'
+
+ LINK_FREE_PATTERN = r'>Enter the code\s*:</em>\s*<span><img src="(.+?)"'
+ LINK_PREMIUM_PATTERN = r'(http://\w+\.fastshare\.cz/download\.php\?id=\d+&)'
+
+ SLOT_ERROR = "> 100% of FREE slots are full"
+ CREDIT_ERROR = " credit for "
+
+
+ def checkErrors(self):
+ if self.SLOT_ERROR in self.html:
+ errmsg = self.info['error'] = _("No free slots")
+ self.retry(12, 60, errmsg)
+
+ if self.CREDIT_ERROR in self.html:
+ errmsg = self.info['error'] = _("Not enough traffic left")
+ self.logWarning(errmsg)
+ self.resetAccount()
+
+ self.info.pop('error', None)
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m:
+ action, captcha_src = m.groups()
+ else:
+ self.error(_("FREE_URL_PATTERN not found"))
+
+ baseurl = "http://www.fastshare.cz"
+ captcha = self.decryptCaptcha(urlparse.urljoin(baseurl, captcha_src))
+ self.download(urlparse.urljoin(baseurl, action), post={'code': captcha, 'btn.x': 77, 'btn.y': 18})
+
+
+ def checkFile(self, rules={}):
+ check = self.checkDownload({
+ 'paralell-dl' : re.compile(r"<title>FastShare.cz</title>|<script>alert\('Pres FREE muzete stahovat jen jeden soubor najednou.'\)"),
+ 'wrong captcha': re.compile(r'Download for FREE'),
+ 'credit' : re.compile(self.CREDIT_ERROR)
+ })
+
+ if check == "paralell-dl":
+ self.retry(6, 10 * 60, _("Paralell download"))
+
+ elif check == "wrong captcha":
+ self.retry(max_tries=5, reason=_("Wrong captcha"))
+
+ elif check == "credit":
+ self.resetAccount()
+
+ return super(FastshareCz, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/FileApeCom.py b/pyload/plugin/hoster/FileApeCom.py
new file mode 100644
index 000000000..87d995817
--- /dev/null
+++ b/pyload/plugin/hoster/FileApeCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FileApeCom(DeadHoster):
+ __name = "FileApeCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?fileape\.com/(index\.php\?act=download\&id=|dl/)\w+'
+ __config = []
+
+ __description = """FileApe.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("espes", "")]
diff --git a/pyload/plugin/hoster/FileSharkPl.py b/pyload/plugin/hoster/FileSharkPl.py
new file mode 100644
index 000000000..9a90352bb
--- /dev/null
+++ b/pyload/plugin/hoster/FileSharkPl.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FileSharkPl(SimpleHoster):
+ __name = "FileSharkPl"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?fileshark\.pl/pobierz/\d+/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FileShark.pl hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", ""),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<h2 class="name-file">(?P<N>.+)</h2>'
+ SIZE_PATTERN = r'<p class="size-file">(.*?)<strong>(?P<S>\d+\.?\d*)\s(?P<U>\w+)</strong></p>'
+ OFFLINE_PATTERN = r'(P|p)lik zosta. (usuni.ty|przeniesiony)'
+
+ LINK_FREE_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-free">'
+ LINK_PREMIUM_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-premium">'
+
+ WAIT_PATTERN = r'var timeToDownload = (\d+);'
+ ERROR_PATTERN = r'<p class="lead text-center alert alert-warning">(.*?)</p>'
+ IP_ERROR_PATTERN = r'Strona jest dost.pna wy..cznie dla u.ytkownik.w znajduj.cych si. na terenie Polski'
+ SLOT_ERROR_PATTERN = r'Osi.gni.to maksymaln. liczb. .ci.ganych jednocze.nie plik.w\.'
+
+ CAPTCHA_PATTERN = r'<img src="data:image/jpeg;base64,(.*?)" title="captcha"'
+ TOKEN_PATTERN = r'name="form\[_token\]" value="(.*?)" />'
+
+
+ def setup(self):
+ self.resumeDownload = True
+
+ if self.premium:
+ self.multiDL = True
+ self.limitDL = 20
+ else:
+ self.multiDL = False
+
+
+ def checkErrors(self):
+ # check if file is now available for download (-> file name can be found in html body)
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ errmsg = self.info['error'] = _("Another download already run")
+ self.retry(15, int(m.group(1)), errmsg)
+
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ alert = m.group(1)
+
+ if re.match(self.IP_ERROR_PATTERN, alert):
+ self.fail(_("Only connections from Polish IP are allowed"))
+
+ elif re.match(self.SLOT_ERROR_PATTERN, alert):
+ errmsg = self.info['error'] = _("No free download slots available")
+ self.logWarning(errmsg)
+ self.retry(10, 30 * 60, _("Still no free download slots available"))
+
+ else:
+ self.info['error'] = alert
+ self.retry(10, 10 * 60, _("Try again later"))
+
+ self.info.pop('error', None)
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download url not found"))
+
+ link = urlparse.urljoin("http://fileshark.pl", m.group(1))
+
+ self.html = self.load(link)
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ seconds = int(m.group(1))
+ self.logDebug("Wait %s seconds" % seconds)
+ self.wait(seconds)
+
+ action, inputs = self.parseHtmlForm('action=""')
+
+ m = re.search(self.TOKEN_PATTERN, self.html)
+ if m is None:
+ self.retry(reason=_("Captcha form not found"))
+
+ inputs['form[_token]'] = m.group(1)
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.retry(reason=_("Captcha image not found"))
+
+ tmp_load = self.load
+ self.load = self._decode64 #: work-around: injects decode64 inside decryptCaptcha
+
+ inputs['form[captcha]'] = self.decryptCaptcha(m.group(1), imgtype='jpeg')
+ inputs['form[start]'] = ""
+
+ self.load = tmp_load
+
+ self.download(link, post=inputs, disposition=True)
+
+
+ def _decode64(self, data, *args, **kwargs):
+ return data.decode('base64')
diff --git a/pyload/plugin/hoster/FileStoreTo.py b/pyload/plugin/hoster/FileStoreTo.py
new file mode 100644
index 000000000..8670577df
--- /dev/null
+++ b/pyload/plugin/hoster/FileStoreTo.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FileStoreTo(SimpleHoster):
+ __name = "FileStoreTo"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FileStore.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ INFO_PATTERN = r'File: <span.*?>(?P<N>.+?)<.*>Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<'
+ TEMP_OFFLINE_PATTERN = r'>Der Download ist nicht bereit !<'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ self.wait(10)
+ self.link = self.load("http://filestore.to/ajax/download.php",
+ get={'D': re.search(r'"D=(\w+)', self.html).group(1)})
diff --git a/pyload/plugin/hoster/FilebeerInfo.py b/pyload/plugin/hoster/FilebeerInfo.py
new file mode 100644
index 000000000..a929a0787
--- /dev/null
+++ b/pyload/plugin/hoster/FilebeerInfo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FilebeerInfo(DeadHoster):
+ __name = "FilebeerInfo"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?filebeer\.info/(?!\d*~f)(?P<ID>\w+)'
+ __config = []
+
+ __description = """Filebeer.info plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/FilecloudIo.py b/pyload/plugin/hoster/FilecloudIo.py
new file mode 100644
index 000000000..0475edeff
--- /dev/null
+++ b/pyload/plugin/hoster/FilecloudIo.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilecloudIo(SimpleHoster):
+ __name = "FilecloudIo"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filecloud.io hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ LOGIN_ACCOUNT = True
+
+ NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?)&nbsp;&nbsp;<'
+ SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}'
+ 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\.(.*?);'
+
+ RECAPTCHA_PATTERN = r'var __recaptcha_public\s*=\s*\'(.+?)\';'
+
+ LINK_FREE_PATTERN = r'"(http://s\d+\.filecloud\.io/%s/\d+/.*?)"'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ data = {"ukey": self.info['pattern']['ID']}
+
+ m = re.search(self.AB1_PATTERN, self.html)
+ if m is None:
+ self.error(_("__AB1"))
+ data['__ab1'] = m.group(1)
+
+ recaptcha = ReCaptcha(self)
+
+ m = re.search(self.RECAPTCHA_PATTERN, self.html)
+ captcha_key = m.group(1) if m else recaptcha.detect_key()
+
+ if captcha_key is None:
+ self.error(_("ReCaptcha key not found"))
+
+ response, challenge = recaptcha.challenge(captcha_key)
+ self.account.form_data = {"recaptcha_challenge_field": challenge,
+ "recaptcha_response_field" : response}
+ self.account.relogin(self.user)
+ self.retry(2)
+
+ json_url = "http://filecloud.io/download-request.json"
+ res = self.load(json_url, post=data)
+ self.logDebug(res)
+ res = json_loads(res)
+
+ if "error" in res and res['error']:
+ self.fail(res)
+
+ self.logDebug(res)
+ if res['captcha']:
+ data['ctype'] = "recaptcha"
+
+ for _i in xrange(5):
+ data['recaptcha_response'], data['recaptcha_challenge'] = recaptcha.challenge(captcha_key)
+
+ json_url = "http://filecloud.io/download-request.json"
+ res = self.load(json_url, post=data)
+ self.logDebug(res)
+ res = json_loads(res)
+
+ if "retry" in res and res['retry']:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail(_("Incorrect captcha"))
+
+ if res['dl']:
+ self.html = self.load('http://filecloud.io/download.html')
+
+ m = re.search(self.LINK_FREE_PATTERN % self.info['pattern']['ID'], self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ if "size" in self.info and self.info['size']:
+ self.check_data = {"size": int(self.info['size'])}
+
+ self.link = m.group(1)
+ else:
+ self.fail(_("Unexpected server response"))
+
+
+ def handle_premium(self, pyfile):
+ akey = self.account.getAccountData(self.user)['akey']
+ ukey = self.info['pattern']['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.link = rep['download_url']
+ else:
+ self.fail(rep['message'])
diff --git a/pyload/plugin/hoster/FilefactoryCom.py b/pyload/plugin/hoster/FilefactoryCom.py
new file mode 100644
index 000000000..6c4e0b807
--- /dev/null
+++ b/pyload/plugin/hoster/FilefactoryCom.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.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
+ yield parseFileInfo(FilefactoryCom, url, getURL(url))
+
+
+class FilefactoryCom(SimpleHoster):
+ __name = "FilefactoryCom"
+ __type = "hoster"
+ __version = "0.55"
+
+ __pattern = r'https?://(?:www\.)?filefactory\.com/(file|trafficshare/\w+)/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filefactory.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ 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'
+ OFFLINE_PATTERN = r'<h2>File Removed</h2>|This file is no longer available|Invalid Download Link'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'"([^"]+filefactory\.com/get.+?)"'
+
+ WAIT_PATTERN = r'<div id="countdown_clock" data-delay="(\d+)">'
+ PREMIUM_ONLY_PATTERN = r'>Premium Account Required'
+
+ COOKIES = [("filefactory.com", "locale", "en_US.utf8")]
+
+
+ def handle_free(self, pyfile):
+ 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(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+
+ self.link = m.group(1)
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(m.group(1))
+
+
+ def checkFile(self, rules={}):
+ 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.error(_("Unknown error"))
+
+ return super(FilefactoryCom, self).checkFile(rules)
+
+
+ def handle_premium(self, pyfile):
+ self.link = self.directLink(self.load(pyfile.url, just_header=True))
+
+ if not self.link:
+ html = self.load(pyfile.url)
+ m = re.search(self.LINK_PREMIUM_PATTERN, html)
+ if m:
+ self.link = m.group(1)
+ else:
+ self.error(_("Premium download link not found"))
diff --git a/pyload/plugin/hoster/FilejungleCom.py b/pyload/plugin/hoster/FilejungleCom.py
new file mode 100644
index 000000000..2fe238de8
--- /dev/null
+++ b/pyload/plugin/hoster/FilejungleCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.FileserveCom import FileserveCom, checkFile
+from pyload.plugin.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"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "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/plugin/hoster/FileomCom.py b/pyload/plugin/hoster/FileomCom.py
new file mode 100644
index 000000000..b01b34db0
--- /dev/null
+++ b/pyload/plugin/hoster/FileomCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://fileom.com/gycaytyzdw3g/random.bin.html
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class FileomCom(XFSHoster):
+ __name = "FileomCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?fileom\.com/\w{12}'
+
+ __description = """Fileom.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'Filename: <span>(?P<N>.+?)<'
+ SIZE_PATTERN = r'File Size: <span class="size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ LINK_PATTERN = r'var url2 = \'(.+?)\';'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
diff --git a/pyload/plugin/hoster/FilepostCom.py b/pyload/plugin/hoster/FilepostCom.py
new file mode 100644
index 000000000..4f7862588
--- /dev/null
+++ b/pyload/plugin/hoster/FilepostCom.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.utils import json_loads
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilepostCom(SimpleHoster):
+ __name = "FilepostCom"
+ __type = "hoster"
+ __version = "0.33"
+
+ __pattern = r'https?://(?:www\.)?(?:filepost\.com/files|fp\.io)/(?P<ID>[^/]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filepost.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<input type="text" id="url" value=\'<a href.*?>(?P<N>[^>]+?) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</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_PATTERN = r'Captcha.init\({\s*key:\s*\'(.+?)\''
+ FLP_TOKEN_PATTERN = r'set_store_options\({token: \'(.+?)\''
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.FLP_TOKEN_PATTERN, self.html)
+ if m is None:
+ self.error(_("Token"))
+ flp_token = m.group(1)
+
+ m = re.search(self.RECAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("Captcha key"))
+ captcha_key = m.group(1)
+
+ # Get wait time
+ get_dict = {'SID': self.req.cj.getCookie('SID'), 'JsHttpRequest': str(int(time.time() * 10000)) + '-xml'}
+ post_dict = {'action': 'set_download', 'token': flp_token, 'code': self.info['pattern']['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": self.info['pattern']['ID'], "file_pass": ''}
+
+ if 'var is_pass_exists = true;' in self.html:
+ # Solve password
+ password = self.getPassword()
+
+ if password:
+ self.logInfo(_("Password protected link, trying ") + file_pass)
+
+ get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
+ post_dict['file_pass'] = file_pass
+
+ self.link = self.getJsonResponse(get_dict, post_dict, 'link')
+
+ if not self.link:
+ self.fail(_("Incorrect password"))
+ else:
+ self.fail(_("No password found"))
+
+ else:
+ # Solve recaptcha
+ recaptcha = ReCaptcha(self)
+
+ for i in xrange(5):
+ get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml'
+ if i:
+ post_dict['recaptcha_response_field'], post_dict['recaptcha_challenge_field'] = recaptcha.challenge(
+ captcha_key)
+ self.logDebug(u"RECAPTCHA: %s : %s : %s" % (
+ captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field']))
+
+ self.link = self.getJsonResponse(get_dict, post_dict, 'link')
+
+ else:
+ self.fail(_("Invalid captcha"))
+
+
+ def getJsonResponse(self, get_dict, post_dict, field):
+ res = json_loads(self.load('https://filepost.com/files/get/', get=get_dict, post=post_dict))
+
+ self.logDebug(res)
+
+ if not 'js' in res:
+ self.error(_("JSON %s 1") % field)
+
+ # i changed js_answer to res['js'] since js_answer is nowhere set.
+ # i don't know the JSON-HTTP specs in detail, but the previous author
+ # accessed res['js']['error'] as well as js_answer['error'].
+ # see the two lines commented out with "# ~?".
+ if 'error' in res['js']:
+
+ if res['js']['error'] == 'download_delay':
+ self.retry(wait_time=res['js']['params']['next_download'])
+ # ~? self.retry(wait_time=js_answer['params']['next_download'])
+
+ elif 'Wrong file password' in res['js']['error'] \
+ or 'You entered a wrong CAPTCHA code' in res['js']['error'] \
+ or 'CAPTCHA Code nicht korrekt' in res['js']['error']:
+ return None
+
+ elif 'CAPTCHA' in res['js']['error']:
+ self.logDebug("Error response is unknown, but mentions CAPTCHA")
+ return None
+
+ else:
+ self.fail(res['js']['error'])
+
+ if not 'answer' in res['js'] or not field in res['js']['answer']:
+ self.error(_("JSON %s 2") % field)
+
+ return res['js']['answer'][field]
diff --git a/pyload/plugin/hoster/FilepupNet.py b/pyload/plugin/hoster/FilepupNet.py
new file mode 100644
index 000000000..6f6689de7
--- /dev/null
+++ b/pyload/plugin/hoster/FilepupNet.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.filepup.net/files/k5w4ZVoF1410184283.html
+# http://www.filepup.net/files/R4GBq9XH1410186553.html
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilepupNet(SimpleHoster):
+ __name = "FilepupNet"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?filepup\.net/files/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filepup.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'>(?P<N>.+?)</h1>'
+ SIZE_PATTERN = r'class="fa fa-archive"></i> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>This file has been deleted'
+
+ LINK_FREE_PATTERN = r'(http://www\.filepup\.net/get/.+?)\''
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link not found"))
+
+ dl_link = m.group(1)
+ self.download(dl_link, post={'task': "download"})
diff --git a/pyload/plugin/hoster/FilerNet.py b/pyload/plugin/hoster/FilerNet.py
new file mode 100644
index 000000000..a0abe97cb
--- /dev/null
+++ b/pyload/plugin/hoster/FilerNet.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://filer.net/get/ivgf5ztw53et3ogd
+# http://filer.net/get/hgo14gzcng3scbvv
+
+import re
+import urlparse
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FilerNet(SimpleHoster):
+ __name = "FilerNet"
+ __type = "hoster"
+ __version = "0.19"
+
+ __pattern = r'https?://(?:www\.)?filer\.net/get/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Filer.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+ 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'
+
+ WAIT_PATTERN = r'musst du <span id="time">(\d+)'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'href="([^"]+)">Get download</a>'
+
+
+ def handle_free(self, pyfile):
+ inputs = self.parseHtmlForm(input_names={'token': re.compile(r'.+')})[1]
+ if 'token' not in inputs:
+ self.error(_("Unable to detect token"))
+
+ self.html = self.load(pyfile.url, post={'token': inputs['token']}, decode=True)
+
+ inputs = self.parseHtmlForm(input_names={'hash': re.compile(r'.+')})[1]
+ if 'hash' not in inputs:
+ self.error(_("Unable to detect hash"))
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+
+ self.load(pyfile.url, post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field': response,
+ 'hash': inputs['hash']}, follow_location=False)
+
+ if 'location' in self.req.http.header.lower():
+ self.link = re.search(r'location: (\S+)', self.req.http.header, re.I).group(1)
+ self.correctCaptcha()
+ else:
+ self.invalidCaptcha()
diff --git a/pyload/plugin/hoster/FilerioCom.py b/pyload/plugin/hoster/FilerioCom.py
new file mode 100644
index 000000000..c55fd2c0b
--- /dev/null
+++ b/pyload/plugin/hoster/FilerioCom.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class FilerioCom(XFSHoster):
+ __name = "FilerioCom"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?(filerio\.(in|com)|filekeen\.com)/\w{12}'
+
+ __description = """FileRio.in hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ URL_REPLACEMENTS = [(r'filekeen\.com', "filerio.in")]
+
+ OFFLINE_PATTERN = r'>&quot;File Not Found|File has been removed'
diff --git a/pyload/plugin/hoster/FilesMailRu.py b/pyload/plugin/hoster/FilesMailRu.py
new file mode 100644
index 000000000..0d7f47536
--- /dev/null
+++ b/pyload/plugin/hoster/FilesMailRu.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import chunks
+
+
+def getInfo(urls):
+ result = []
+ for chunk in chunks(urls, 10):
+ for url in chunk:
+ html = getURL(url)
+ if r'<div class="errorMessage mb10">' in html:
+ result.append((url, 0, 1, url))
+ elif r'Page cannot be displayed' in html:
+ 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, html).group(0).split(', event)">')[1].split('</a>')[0]
+ result.append((file_name, 0, 2, url))
+ except Exception:
+ 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.32"
+
+ __pattern = r'http://(?:www\.)?files\.mail\.ru/.+'
+
+ __description = """Files.mail.ru hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("oZiRiz", "ich@oziriz.de")]
+
+
+ def setup(self):
+ self.multiDL = bool(self.account)
+
+
+ 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/plugin/hoster/FileserveCom.py b/pyload/plugin/hoster/FileserveCom.py
new file mode 100644
index 000000000..e368adbcc
--- /dev/null
+++ b/pyload/plugin/hoster/FileserveCom.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import chunks
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import secondsToMidnight
+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.S):
+ 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.54"
+
+ __pattern = r'http://(?:www\.)?fileserve\.com/file/(?P<ID>[^/]+)'
+
+ __description = """Fileserve.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("Paul King", ""),
+ ("zoidberg", "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=\'(.+?)\''
+ 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.handle_premium(pyfile)
+ else:
+ self.handle_free(pyfile)
+
+
+ def handle_free(self, pyfile):
+ 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.error(_("Unknown server response"))
+
+ # show download link
+ res = self.load(self.url, post={"downloadLink": "show"}, decode=True)
+ self.logDebug("Show downloadLink response: %s" % res)
+ if "fail" in res:
+ self.error(_("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):
+ res = self.load(self.url, post={"downloadLink": "wait"}, decode=True)
+ self.logDebug("Wait response: %s" % res[:80])
+
+ if "fail" in res:
+ self.fail(_("Failed getting wait time"))
+
+ if self.getClassName() == "FilejungleCom":
+ m = re.search(r'"waitTime":(\d+)', res)
+ if m is None:
+ self.fail(_("Cannot get wait time"))
+ wait_time = int(m.group(1))
+ else:
+ wait_time = int(res) + 3
+
+ self.setWait(wait_time)
+ self.wait()
+
+
+ def doCaptcha(self):
+ captcha_key = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group(1)
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge(captcha_key)
+ res = json_loads(self.load(self.URLS[2],
+ post={'recaptcha_challenge_field' : challenge,
+ 'recaptcha_response_field' : response,
+ 'recaptcha_shortencode_field': self.file_id}))
+ if not res['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 handle_premium(self, pyfile):
+ premium_url = None
+ if self.getClassName() == "FileserveCom":
+ # try api download
+ res = 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 res:
+ res = json_loads(res)
+ if res['error_code'] == "302":
+ premium_url = res['next']
+ elif res['error_code'] in ["305", "500"]:
+ self.tempOffline()
+ elif res['error_code'] in ["403", "605"]:
+ self.resetAccount()
+ elif res['error_code'] in ["606", "607", "608"]:
+ self.offline()
+ else:
+ self.logError(res['error_code'], res['error_message'])
+
+ self.download(premium_url or pyfile.url)
+
+ if not premium_url and self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)}):
+ 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/plugin/hoster/FileshareInUa.py b/pyload/plugin/hoster/FileshareInUa.py
new file mode 100644
index 000000000..8b09a086a
--- /dev/null
+++ b/pyload/plugin/hoster/FileshareInUa.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FileshareInUa(DeadHoster):
+ __name = "FileshareInUa"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?fileshare\.in\.ua/\w{7}'
+ __config = []
+
+ __description = """Fileshare.in.ua hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fwannmacher", "felipe@warhammerproject.com")]
diff --git a/pyload/plugin/hoster/FilesonicCom.py b/pyload/plugin/hoster/FilesonicCom.py
new file mode 100644
index 000000000..f02f7bff6
--- /dev/null
+++ b/pyload/plugin/hoster/FilesonicCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FilesonicCom(DeadHoster):
+ __name = "FilesonicCom"
+ __type = "hoster"
+ __version = "0.35"
+
+ __pattern = r'http://(?:www\.)?filesonic\.com/file/\w+'
+ __config = []
+
+ __description = """Filesonic.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("paulking", "")]
diff --git a/pyload/plugin/hoster/FilezyNet.py b/pyload/plugin/hoster/FilezyNet.py
new file mode 100644
index 000000000..bd6ac5984
--- /dev/null
+++ b/pyload/plugin/hoster/FilezyNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FilezyNet(DeadHoster):
+ __name = "FilezyNet"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?filezy\.net/\w{12}'
+ __config = []
+
+ __description = """Filezy.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
diff --git a/pyload/plugin/hoster/FiredriveCom.py b/pyload/plugin/hoster/FiredriveCom.py
new file mode 100644
index 000000000..a5e56191f
--- /dev/null
+++ b/pyload/plugin/hoster/FiredriveCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FiredriveCom(DeadHoster):
+ __name = "FiredriveCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?(firedrive|putlocker)\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
+ __config = []
+
+ __description = """Firedrive.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/FlyFilesNet.py b/pyload/plugin/hoster/FlyFilesNet.py
new file mode 100644
index 000000000..4f1d71d21
--- /dev/null
+++ b/pyload/plugin/hoster/FlyFilesNet.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FlyFilesNet(SimpleHoster):
+ __name = "FlyFilesNet"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?flyfiles\.net/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FlyFiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+ SESSION_PATTERN = r'flyfiles\.net/(.*)/.*'
+ NAME_PATTERN = r'flyfiles\.net/.*/(.*)'
+
+
+ def process(self, pyfile):
+ name = re.search(self.NAME_PATTERN, pyfile.url).group(1)
+ pyfile.name = urllib.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})
+ 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()
+
+ self.link = parsed_url.replace('#downlink|', '')
diff --git a/pyload/plugin/hoster/FourSharedCom.py b/pyload/plugin/hoster/FourSharedCom.py
new file mode 100644
index 000000000..cda3bb7a3
--- /dev/null
+++ b/pyload/plugin/hoster/FourSharedCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class FourSharedCom(SimpleHoster):
+ __name = "FourSharedCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """4Shared.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<meta name="title" content="(?P<N>.+?)"'
+ SIZE_PATTERN = r'<span title="Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)">'
+ OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted.'
+
+ NAME_REPLACEMENTS = [(r"&#(\d+).", lambda m: unichr(int(m.group(1))))]
+ SIZE_REPLACEMENTS = [(",", "")]
+
+ DIRECT_LINK = False
+ LOGIN_ACCOUNT = True
+
+ LINK_FREE_PATTERN = r'name="d3link" value="(.*?)"'
+ LINK_BTN_PATTERN = r'id="btnLink" href="(.*?)"'
+
+ ID_PATTERN = r'name="d3fid" value="(.*?)"'
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.LINK_BTN_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ else:
+ link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', pyfile.url)
+
+ self.html = self.load(link)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link"))
+
+ self.link = m.group(1)
+
+ try:
+ m = re.search(self.ID_PATTERN, self.html)
+ res = self.load('http://www.4shared.com/web/d2/getFreeDownloadLimitInfo?fileId=%s' % m.group(1))
+ self.logDebug(res)
+ except Exception:
+ pass
+
+ self.wait(20)
diff --git a/pyload/plugin/hoster/FreakshareCom.py b/pyload/plugin/hoster/FreakshareCom.py
new file mode 100644
index 000000000..6cf447128
--- /dev/null
+++ b/pyload/plugin/hoster/FreakshareCom.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import secondsToMidnight
+
+
+class FreakshareCom(Hoster):
+ __name = "FreakshareCom"
+ __type = "hoster"
+ __version = "0.40"
+
+ __pattern = r'http://(?:www\.)?freakshare\.(net|com)/files/\S*?/'
+
+ __description = """Freakshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sitacuisses", "sitacuisses@yahoo.de"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("Toilal", "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.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:
+ m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html)
+ if m:
+ file_name = m.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:
+ m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html)
+ if m:
+ units = float(m.group(1).replace(",", ""))
+ pow = {'KB': 1, 'MB': 2, 'GB': 3}[m.group(2)]
+ size = int(units * (2 ** 20) ** 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))
+ 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):
+ 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
+
+ to_sort = re.findall(r"<input\stype=\".*?\"\svalue=\"(\S*?)\".*?name=\"(\S*?)\"\s.*?\/>", herewego)
+ request_options = dict((n, v) for (v, n) in to_sort)
+
+ challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=(\w+)", 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/plugin/hoster/FreeWayMe.py b/pyload/plugin/hoster/FreeWayMe.py
new file mode 100644
index 000000000..1459bb858
--- /dev/null
+++ b/pyload/plugin/hoster/FreeWayMe.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class FreeWayMe(MultiHoster):
+ __name = "FreeWayMe"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'https://(?:www\.)?free-way\.me/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FreeWayMe multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Nicolas Giese", "james@free-way.me")]
+
+
+ def setup(self):
+ self.resumeDownload = False
+ self.multiDL = self.premium
+ self.chunkLimit = 1
+
+
+ def handle_premium(self, pyfile):
+ user, data = self.account.selectAccount()
+
+ for _i in xrange(5):
+ # try it five times
+ header = self.load("https://www.free-way.me/load.php",
+ get={'multiget': 7,
+ 'url' : pyfile.url,
+ 'user' : user,
+ 'pw' : self.account.getAccountData(user)['password'],
+ 'json' : ""},
+ just_header=True)
+
+ if 'location' in header:
+ headers = self.load(header['location'], just_header=True)
+ if headers['code'] == 500:
+ # error on 2nd stage
+ self.logError(_("Error [stage2]"))
+ else:
+ # seems to work..
+ self.download(header['location'])
+ break
+ else:
+ # error page first stage
+ self.logError(_("Error [stage1]"))
+
+ #@TODO: handle errors
diff --git a/pyload/plugin/hoster/FreevideoCz.py b/pyload/plugin/hoster/FreevideoCz.py
new file mode 100644
index 000000000..f616097f2
--- /dev/null
+++ b/pyload/plugin/hoster/FreevideoCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class FreevideoCz(DeadHoster):
+ __name = "FreevideoCz"
+ __type = "hoster"
+ __version = "0.30"
+
+ __pattern = r'http://(?:www\.)?freevideo\.cz/vase-videa/.+'
+ __config = []
+
+ __description = """Freevideo.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/FshareVn.py b/pyload/plugin/hoster/FshareVn.py
new file mode 100644
index 000000000..b44553149
--- /dev/null
+++ b/pyload/plugin/hoster/FshareVn.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.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)
+
+ yield parseFileInfo(FshareVn, url, html)
+
+
+def doubleDecode(m):
+ return m.group(1).decode('raw_unicode_escape')
+
+
+class FshareVn(SimpleHoster):
+ __name = "FshareVn"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?fshare\.vn/file/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """FshareVn hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<p>(?P<N>[^<]+)<\\/p>[\\trn\s]*<p>(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)<\\/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>'
+
+ NAME_REPLACEMENTS = [("(.*)", doubleDecode)]
+
+ LINK_FREE_PATTERN = r'action="(http://download.*?)[#"]'
+ WAIT_PATTERN = ur'Lượt tải xuống kế tiếp là:\s*(.*?)\s*<'
+
+
+ def preload(self):
+ self.html = self.load("http://www.fshare.vn/check_link.php",
+ post={'action': "check_link", 'arrlinks': pyfile.url},
+ decode=True)
+
+ if isinstance(self.TEXT_ENCODING, basestring):
+ self.html = unicode(self.html, self.TEXT_ENCODING)
+
+
+ def handle_free(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+
+ self.checkErrors()
+
+ action, inputs = self.parseHtmlForm('frm_download')
+ url = urlparse.urljoin(pyfile.url, action)
+
+ if not inputs:
+ self.error(_("No FORM"))
+
+ elif 'link_file_pwd_dl' in inputs:
+ password = self.getPassword()
+
+ if password:
+ self.logInfo(_("Password protected link, trying ") + password)
+ inputs['link_file_pwd_dl'] = password
+ self.html = self.load(url, post=inputs, decode=True)
+
+ if 'name="link_file_pwd_dl"' in self.html:
+ self.fail(_("Incorrect password"))
+ else:
+ self.fail(_("No password found"))
+
+ else:
+ self.html = self.load(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_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = m.group(1)
+ self.wait()
+
+
+ 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 = time.mktime.time(time.strptime.time(m.group(1), "%d/%m/%Y %H:%M"))
+ self.wait(wait_until - time.mktime.time(time.gmtime.time()) - 7 * 60 * 60, True)
+ self.retry()
+ elif '<ul class="message-error">' in self.html:
+ msg = "Unknown error occured or wait time not parsed"
+ self.logError(msg)
+ self.retry(30, 2 * 60, msg)
+
+ self.info.pop('error', None)
diff --git a/pyload/plugin/hoster/Ftp.py b/pyload/plugin/hoster/Ftp.py
new file mode 100644
index 000000000..d26e3ad0b
--- /dev/null
+++ b/pyload/plugin/hoster/Ftp.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+import urllib
+import urlparse
+
+from pyload.plugin.Hoster import Hoster
+
+
+class Ftp(Hoster):
+ __name = "Ftp"
+ __type = "hoster"
+ __version = "0.51"
+
+ __pattern = r'(?:ftps?|sftp)://([\w.-]+(:[\w.-]+)?@)?[\w.-]+(:\d+)?/.+'
+
+ __description = """Download from ftp directory"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.com"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+ def process(self, pyfile):
+ parsed_url = urlparse.urlparse(pyfile.url)
+ netloc = parsed_url.netloc
+
+ pyfile.name = parsed_url.path.rpartition('/')[2]
+ try:
+ pyfile.name = urllib.unquote(str(pyfile.name)).decode('utf8')
+ except Exception:
+ 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.getAccountInfo(netloc)['password'])
+ else:
+ pwd = self.getPassword()
+ if ':' in pwd:
+ self.req.addAuth(pwd)
+
+ self.req.http.c.setopt(pycurl.NOBODY, 1)
+
+ try:
+ res = 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+)", res)
+ 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.urlparse(pyfile.url).path.rpartition('/')[2])
+ pyfile.url += '/'
+ self.req.http.c.setopt(48, 1) #: CURLOPT_DIRLISTONLY
+ res = self.load(pyfile.url, decode=False)
+ links = [pyfile.url + urllib.quote(x) for x in res.splitlines()]
+ self.logDebug("LINKS", links)
+ self.core.api.addPackage(pkgname, links)
+ else:
+ self.fail(_("Unexpected server response"))
diff --git a/pyload/plugin/hoster/GamefrontCom.py b/pyload/plugin/hoster/GamefrontCom.py
new file mode 100644
index 000000000..81568e376
--- /dev/null
+++ b/pyload/plugin/hoster/GamefrontCom.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.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/\w+'
+
+ __description = """Gamefront.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fwannmacher", "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 = True
+ 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(_("Plugin broken")
+
+ return name.group(1)
+
+
+ def _getLink(self):
+ self.html2 = self.load("http://www.gamefront.com/" + re.search("(files/service/thankyou\\?id=\w+)",
+ self.html).group(1))
+ return re.search("<a href=\"(http://media\d+\.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/plugin/hoster/GigapetaCom.py b/pyload/plugin/hoster/GigapetaCom.py
new file mode 100644
index 000000000..668760d17
--- /dev/null
+++ b/pyload/plugin/hoster/GigapetaCom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class GigapetaCom(SimpleHoster):
+ __name = "GigapetaCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?gigapeta\.com/dl/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """GigaPeta.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<img src=".*" alt="file" />-->\s*(?P<N>.*?)\s*</td>'
+ SIZE_PATTERN = r'<th>\s*Size\s*</th>\s*<td>\s*(?P<S>.*?)\s*</td>'
+ OFFLINE_PATTERN = r'<div id="page_error">'
+
+ COOKIES = [("gigapeta.com", "lang", "us")]
+
+
+ def handle_free(self, pyfile):
+ captcha_key = str(random.randint(1, 100000000))
+ captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key
+
+ for _i in xrange(5):
+ self.checkErrors()
+
+ captcha = self.decryptCaptcha(captcha_url)
+ self.html = self.load(pyfile.url,
+ post={'captcha_key': captcha_key,
+ 'captcha' : captcha,
+ 'download' : "Download"},
+ follow_location=False)
+
+ m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I)
+ if m:
+ self.link = 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"))
+
+
+ def checkErrors(self):
+ if "All threads for IP" in self.html:
+ self.logDebug("Your IP is already downloading a file")
+ self.wait(5 * 60, True)
+ self.retry()
+
+ self.info.pop('error', None)
diff --git a/pyload/plugin/hoster/GooIm.py b/pyload/plugin/hoster/GooIm.py
new file mode 100644
index 000000000..50e16de4b
--- /dev/null
+++ b/pyload/plugin/hoster/GooIm.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# https://goo.im/devs/liquidsmooth/3.x/codina/Nightly/LS-KK-v3.2-2014-08-01-codina.zip
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class GooIm(SimpleHoster):
+ __name = "GooIm"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?goo\.im/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Goo.im hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ 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.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ self.wait(10)
+ self.link = pyfile.url
diff --git a/pyload/plugin/hoster/GoogledriveCom.py b/pyload/plugin/hoster/GoogledriveCom.py
new file mode 100644
index 000000000..8c94bd9b4
--- /dev/null
+++ b/pyload/plugin/hoster/GoogledriveCom.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# https://drive.google.com/file/d/0B6RNTe4ygItBQm15RnJiTmMyckU/view?pli=1
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import html_unescape
+
+
+class GoogledriveCom(SimpleHoster):
+ __name = "GoogledriveCom"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'https?://(?:www\.)?drive\.google\.com/file/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Drive.google.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ DISPOSITION = False #: Remove in 0.4.10
+
+ NAME_PATTERN = r'"og:title" content="(?P<N>.*?)">'
+ OFFLINE_PATTERN = r'align="center"><p class="errorMessage"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ try:
+ link1 = re.search(r'"(https://docs.google.com/uc\?id.*?export=download)",',
+ self.html.decode('unicode-escape')).group(1)
+
+ except AttributeError:
+ self.error(_("Hop #1 not found"))
+
+ else:
+ self.logDebug("Next hop: %s" % link1)
+
+ self.html = self.load(link1).decode('unicode-escape')
+
+ try:
+ link2 = html_unescape(re.search(r'href="(/uc\?export=download.*?)">',
+ self.html).group(1))
+
+ except AttributeError:
+ self.error(_("Hop #2 not found"))
+
+ else:
+ self.logDebug("Next hop: %s" % link2)
+
+ link3 = self.load("https://docs.google.com" + link2, just_header=True)
+ self.logDebug("DL-Link: %s" % link3['location'])
+
+ self.link = link3['location']
diff --git a/pyload/plugin/hoster/HellshareCz.py b/pyload/plugin/hoster/HellshareCz.py
new file mode 100644
index 000000000..0e909c599
--- /dev/null
+++ b/pyload/plugin/hoster/HellshareCz.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class HellshareCz(SimpleHoster):
+ __name = "HellshareCz"
+ __type = "hoster"
+ __version = "0.85"
+
+ __pattern = r'http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Hellshare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ CHECK_TRAFFIC = True
+ LOGIN_ACCOUNT = True
+
+ NAME_PATTERN = r'<h1 id="filename"[^>]*>(?P<N>[^<]+)</h1>'
+ SIZE_PATTERN = r'<strong id="FileSize_master">(?P<S>[\d.,]+)&nbsp;(?P<U>[\w^_]+)</strong>'
+ OFFLINE_PATTERN = r'<h1>File not found.</h1>'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = bool(self.account)
+ self.chunkLimit = 1
diff --git a/pyload/plugin/hoster/HellspyCz.py b/pyload/plugin/hoster/HellspyCz.py
new file mode 100644
index 000000000..b64becf9c
--- /dev/null
+++ b/pyload/plugin/hoster/HellspyCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class HellspyCz(DeadHoster):
+ __name = "HellspyCz"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?(?:hellspy\.(?:cz|com|sk|hu|pl)|sciagaj\.pl)(/\S+/\d+)'
+ __config = []
+
+ __description = """HellSpy.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/HostujeNet.py b/pyload/plugin/hoster/HostujeNet.py
new file mode 100644
index 000000000..ffe9243ef
--- /dev/null
+++ b/pyload/plugin/hoster/HostujeNet.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class HostujeNet(SimpleHoster):
+ __name = "HostujeNet"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?hostuje\.net/\w+'
+
+ __description = """Hostuje.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("GammaC0de", None)]
+
+
+ NAME_PATTERN = r'<input type="hidden" name="name" value="(?P<N>.+?)">'
+ SIZE_PATTERN = r'<b>Rozmiar:</b> (?P<S>[\d.,]+) (?P<U>[\w^_]+)<br>'
+ OFFLINE_PATTERN = ur'Podany plik nie został odnaleziony\.\.\.'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ m = re.search(r'<script src="([\w^_]+.php)"></script>', self.html)
+ if m:
+ jscript = self.load("http://hostuje.net/" + m.group(1))
+ m = re.search(r"\('(\w+\.php\?i=\w+)'\);", jscript)
+ if m:
+ self.load("http://hostuje.net/" + m.group(1))
+ else:
+ self.error(_("unexpected javascript format"))
+ else:
+ self.error(_("script not found"))
+
+ action, inputs = self.parseHtmlForm(pyfile.url.replace(".", "\.").replace( "?", "\?"))
+ if not action:
+ self.error(_("form not found"))
+
+ self.download(action, post=inputs)
diff --git a/pyload/plugin/hoster/HotfileCom.py b/pyload/plugin/hoster/HotfileCom.py
new file mode 100644
index 000000000..743c24e23
--- /dev/null
+++ b/pyload/plugin/hoster/HotfileCom.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class HotfileCom(DeadHoster):
+ __name = "HotfileCom"
+ __type = "hoster"
+ __version = "0.37"
+
+ __pattern = r'https?://(?:www\.)?hotfile\.com/dl/\d+/\w+'
+ __config = []
+
+ __description = """Hotfile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sitacuisses", "sitacuisses@yhoo.de"),
+ ("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("JoKoT3", "jokot3@gmail.com")]
diff --git a/pyload/plugin/hoster/HugefilesNet.py b/pyload/plugin/hoster/HugefilesNet.py
new file mode 100644
index 000000000..25a337739
--- /dev/null
+++ b/pyload/plugin/hoster/HugefilesNet.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class HugefilesNet(XFSHoster):
+ __name = "HugefilesNet"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?hugefiles\.net/\w{12}'
+
+ __description = """Hugefiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ SIZE_PATTERN = r'File Size:</span>\s*<span.*?>(?P<S>[^<]+)</span></div>'
+
+ FORM_INPUTS_MAP = {'ctype': re.compile(r'\d+')}
diff --git a/pyload/plugin/hoster/HundredEightyUploadCom.py b/pyload/plugin/hoster/HundredEightyUploadCom.py
new file mode 100644
index 000000000..de312245e
--- /dev/null
+++ b/pyload/plugin/hoster/HundredEightyUploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class HundredEightyUploadCom(XFSHoster):
+ __name = "HundredEightyUploadCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?180upload\.com/\w{12}'
+
+ __description = """180upload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ OFFLINE_PATTERN = r'>File Not Found'
diff --git a/pyload/plugin/hoster/IFileWs.py b/pyload/plugin/hoster/IFileWs.py
new file mode 100644
index 000000000..57c86d490
--- /dev/null
+++ b/pyload/plugin/hoster/IFileWs.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class IFileWs(DeadHoster):
+ __name = "IFileWs"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?ifile\.ws/\w{12}'
+ __config = []
+
+ __description = """Ifile.ws hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
diff --git a/pyload/plugin/hoster/IcyFilesCom.py b/pyload/plugin/hoster/IcyFilesCom.py
new file mode 100644
index 000000000..88ec57a98
--- /dev/null
+++ b/pyload/plugin/hoster/IcyFilesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class IcyFilesCom(DeadHoster):
+ __name = "IcyFilesCom"
+ __type = "hoster"
+ __version = "0.06"
+
+ __pattern = r'http://(?:www\.)?icyfiles\.com/(.+)'
+ __config = []
+
+ __description = """IcyFiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
diff --git a/pyload/plugin/hoster/IfileIt.py b/pyload/plugin/hoster/IfileIt.py
new file mode 100644
index 000000000..647e06c01
--- /dev/null
+++ b/pyload/plugin/hoster/IfileIt.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class IfileIt(DeadHoster):
+ __name = "IfileIt"
+ __type = "hoster"
+ __version = "0.29"
+
+ __pattern = r'^unmatchable$'
+ __config = []
+
+ __description = """Ifile.it"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/IfolderRu.py b/pyload/plugin/hoster/IfolderRu.py
new file mode 100644
index 000000000..4cbddfaa3
--- /dev/null
+++ b/pyload/plugin/hoster/IfolderRu.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class IfolderRu(SimpleHoster):
+ __name = "IfolderRu"
+ __type = "hoster"
+ __version = "0.39"
+
+ __pattern = r'http://(?:www)?(files\.)?(ifolder\.ru|metalarea\.org|rusfolder\.(com|net|ru))/(files/)?(?P<ID>\d+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Ifolder.ru hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ SIZE_REPLACEMENTS = [(u'Кб', 'KB'), (u'Мб', 'MB'), (u'Гб', 'GB')]
+
+ NAME_PATTERN = ur'(?:<div><span>)?НазваМОе:(?:</span>)? <b>(?P<N>[^<]+)</b><(?:/div|br)>'
+ SIZE_PATTERN = ur'(?:<div><span>)?РазЌер:(?:</span>)? <b>(?P<S>[^<]+)</b><(?:/div|br)>'
+ OFFLINE_PATTERN = ur'<p>Ѐайл МПЌер <b>.*?</b> (Ме МайЎеМ|уЎалеМ) !!!</p>'
+
+ SESSION_ID_PATTERN = r'<input type="hidden" name="session" value="(.+?)"'
+ INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag\.value = "(.+?)";\}'
+ HIDDEN_INPUT_PATTERN = r'var v = .*?name=\'(.+?)\' value=\'1\''
+
+ LINK_FREE_PATTERN = r'<a href="(.+?)" class="downloadbutton_files"'
+
+ WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МеверМый кПЎ,<br>ввеЎОте еще раз</font><br>'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = bool(self.account)
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ url = "http://rusfolder.com/%s" % self.info['pattern']['ID']
+ self.html = self.load("http://rusfolder.com/%s" % self.info['pattern']['ID'], decode=True)
+ self.getFileInfo()
+
+ session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups()
+
+ captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id
+ for _i in xrange(5):
+ action, inputs = self.parseHtmlForm('id="download-step-one-form"')
+ inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True)
+ inputs['action'] = '1'
+ self.logDebug(inputs)
+
+ self.html = self.load(url, decode=True, post=inputs)
+ if self.WRONG_CAPTCHA_PATTERN in self.html:
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail(_("Invalid captcha"))
+
+ self.link = re.search(self.LINK_FREE_PATTERN, self.html).group(1)
diff --git a/pyload/plugin/hoster/JumbofilesCom.py b/pyload/plugin/hoster/JumbofilesCom.py
new file mode 100644
index 000000000..47a8ebca1
--- /dev/null
+++ b/pyload/plugin/hoster/JumbofilesCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class JumbofilesCom(SimpleHoster):
+ __name = "JumbofilesCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?jumbofiles\.com/(?P<ID>\w{12})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """JumboFiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com")]
+
+
+ INFO_PATTERN = r'<TR><TD>(?P<N>[^<]+?)\s*<small>\((?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'Not Found or Deleted / Disabled due to inactivity or DMCA'
+ LINK_FREE_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ post_data = {"id": self.info['pattern']['ID'], "op": "download3", "rand": ""}
+ html = self.load(self.pyfile.url, post=post_data, decode=True)
+ self.link = re.search(self.LINK_FREE_PATTERN, html).group(1)
diff --git a/pyload/plugin/hoster/JunocloudMe.py b/pyload/plugin/hoster/JunocloudMe.py
new file mode 100644
index 000000000..9054734fb
--- /dev/null
+++ b/pyload/plugin/hoster/JunocloudMe.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class JunocloudMe(XFSHoster):
+ __name = "JunocloudMe"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:\w+\.)?junocloud\.me/\w{12}'
+
+ __description = """Junocloud.me hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ URL_REPLACEMENTS = [(r'//(www\.)?junocloud', "//dl3.junocloud")]
+
+ OFFLINE_PATTERN = r'>No such file with this filename<'
+ TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
diff --git a/pyload/plugin/hoster/Keep2ShareCc.py b/pyload/plugin/hoster/Keep2ShareCc.py
new file mode 100644
index 000000000..464cac9bb
--- /dev/null
+++ b/pyload/plugin/hoster/Keep2ShareCc.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class Keep2ShareCc(SimpleHoster):
+ __name = "Keep2ShareCc"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Keep2Share.cc hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", "http://keep2s.cc/file/\g<ID>")]
+
+ NAME_PATTERN = r'File: <span>(?P<N>.+)</span>'
+ SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>'
+
+ OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted|Error 404'
+ TEMP_OFFLINE_PATTERN = r'Downloading blocked due to'
+
+ LINK_FREE_PATTERN = r'"(.+?url.html\?file=.+?)"|window\.location\.href = \'(.+?)\';'
+ LINK_PREMIUM_PATTERN = r'window\.location\.href = \'(.+?)\';'
+
+ CAPTCHA_PATTERN = r'src="(/file/captcha\.html.+?)"'
+
+ WAIT_PATTERN = r'Please wait ([\d:]+) to download this file'
+ TEMP_ERROR_PATTERN = r'>\s*(Download count files exceed|Traffic limit exceed|Free account does not allow to download more than one file at the same time)'
+ ERROR_PATTERN = r'>\s*(Free user can\'t download large files|You no can access to this file|This download available only for premium users|This is private file)'
+
+
+ def checkErrors(self):
+ m = re.search(self.TEMP_ERROR_PATTERN, self.html)
+ if m:
+ self.info['error'] = m.group(1)
+ self.wantReconnect = True
+ self.retry(wait_time=30 * 60, reason=m.group(0))
+
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ errmsg = self.info['error'] = m.group(1)
+ self.error(errmsg)
+
+ 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.wantReconnect = True
+ self.retry(wait_time=wait_time, reason="Please wait to download this file")
+
+ self.info.pop('error', None)
+
+
+ def handle_free(self, pyfile):
+ self.fid = re.search(r'<input type="hidden" name="slow_id" value="(.+?)">', self.html).group(1)
+ self.html = self.load(pyfile.url, post={'yt0': '', 'slow_id': self.fid})
+
+ # self.logDebug(self.fid)
+ # self.logDebug(pyfile.url)
+
+ self.checkErrors()
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.handleCaptcha()
+ self.wait(31)
+ self.html = self.load(pyfile.url)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+
+ self.link = m.group(1)
+
+
+ def handleCaptcha(self):
+ post_data = {'free' : 1,
+ 'freeDownloadRequest': 1,
+ 'uniqueId' : self.fid,
+ 'yt0' : ''}
+
+ m = re.search(r'id="(captcha\-form)"', self.html)
+ self.logDebug("captcha-form found %s" % m)
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ self.logDebug("CAPTCHA_PATTERN found %s" % m)
+ if m:
+ captcha_url = urlparse.urljoin("http://keep2s.cc/", m.group(1))
+ post_data['CaptchaForm[code]'] = self.decryptCaptcha(captcha_url)
+ else:
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+ post_data.update({'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ self.html = self.load(self.pyfile.url, post=post_data)
+
+ if 'verification code is incorrect' not in self.html:
+ self.correctCaptcha()
+ else:
+ self.invalidCaptcha()
diff --git a/pyload/plugin/hoster/KickloadCom.py b/pyload/plugin/hoster/KickloadCom.py
new file mode 100644
index 000000000..11c79cef9
--- /dev/null
+++ b/pyload/plugin/hoster/KickloadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class KickloadCom(DeadHoster):
+ __name = "KickloadCom"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?kickload\.com/get/.+'
+ __config = []
+
+ __description = """Kickload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/hoster/KingfilesNet.py b/pyload/plugin/hoster/KingfilesNet.py
new file mode 100644
index 000000000..b8223ceb6
--- /dev/null
+++ b/pyload/plugin/hoster/KingfilesNet.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class KingfilesNet(SimpleHoster):
+ __name = "KingfilesNet"
+ __type = "hoster"
+ __version = "0.07"
+
+ __pattern = r'http://(?:www\.)?kingfiles\.net/(?P<ID>\w{12})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Kingfiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'name="fname" value="(?P<N>.+?)">'
+ SIZE_PATTERN = r'>Size: .+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>(File Not Found</b><br><br>|File Not Found</h2>)'
+
+ RAND_ID_PATTERN = r'type=\"hidden\" name=\"rand\" value=\"(.+)\">'
+
+ LINK_FREE_PATTERN = r'var download_url = \'(.+)\';'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ # Click the free user button
+ post_data = {'op' : "download1",
+ 'usr_login' : "",
+ 'id' : self.info['pattern']['ID'],
+ 'fname' : pyfile.name,
+ 'referer' : "",
+ 'method_free': "+"}
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ solvemedia = SolveMedia(self)
+ response, challenge = solvemedia.challenge()
+
+ # Make the downloadlink appear and load the file
+ m = re.search(self.RAND_ID_PATTERN, self.html)
+ if m is None:
+ self.error(_("Random key not found"))
+
+ rand = m.group(1)
+ self.logDebug("rand = ", rand)
+
+ post_data = {'op' : "download2",
+ 'id' : self.info['pattern']['ID'],
+ 'rand' : rand,
+ 'referer' : pyfile.url,
+ 'method_free' : "+",
+ 'method_premium' : "",
+ 'adcopy_response' : response,
+ 'adcopy_challenge': challenge,
+ 'down_direct' : "1"}
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download url not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/LemUploadsCom.py b/pyload/plugin/hoster/LemUploadsCom.py
new file mode 100644
index 000000000..c43867a99
--- /dev/null
+++ b/pyload/plugin/hoster/LemUploadsCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class LemUploadsCom(DeadHoster):
+ __name = "LemUploadsCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?lemuploads\.com/\w{12}'
+ __config = []
+
+ __description = """LemUploads.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/LetitbitNet.py b/pyload/plugin/hoster/LetitbitNet.py
new file mode 100644
index 000000000..9751c2658
--- /dev/null
+++ b/pyload/plugin/hoster/LetitbitNet.py
@@ -0,0 +1,136 @@
+# -*- 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
+import urlparse
+
+from pyload.utils import json_loads, json_dumps
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+
+
+def api_response(url):
+ json_data = ["yw7XQy2v9", ["download/info", {"link": url}]]
+ api_rep = getURL("http://api.letitbit.net/json",
+ post={'r': json_dumps(json_data)})
+ return json_loads(api_rep)
+
+
+def getInfo(urls):
+ for url in urls:
+ api_rep = api_response(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.30"
+
+ __pattern = r'https?://(?:www\.)?(letitbit|shareflare)\.net/download/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Letitbit.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("z00nx", "z00nx0@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "letitbit.net")]
+
+ SECONDS_PATTERN = r'seconds\s*=\s*(\d+);'
+ CAPTCHA_CONTROL_FIELD = r'recaptcha_control_field\s=\s\'(.+?)\''
+
+
+ def setup(self):
+ self.resumeDownload = True
+
+
+ def handle_free(self, pyfile):
+ action, inputs = self.parseHtmlForm('id="ifree_form"')
+ if not action:
+ self.error(_("ifree_form"))
+
+ pyfile.size = float(inputs['sssize'])
+ self.logDebug(action, inputs)
+ inputs['desc'] = ""
+
+ self.html = self.load(urlparse.urljoin("http://letitbit.net/", action), post=inputs)
+
+ 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)
+
+ res = self.load("http://letitbit.net/ajax/download3.php", post=" ")
+ if res != '1':
+ self.error(_("Unknown response - ajax_check_url"))
+
+ self.logDebug(res)
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+
+ post_data = {"recaptcha_challenge_field": challenge,
+ "recaptcha_response_field": response,
+ "recaptcha_control_field": recaptcha_control_field}
+
+ self.logDebug("Post data to send", post_data)
+
+ res = self.load("http://letitbit.net/ajax/check_recaptcha.php", post=post_data)
+
+ self.logDebug(res)
+
+ if not res:
+ self.invalidCaptcha()
+
+ if res == "error_free_download_blocked":
+ self.logWarning(_("Daily limit reached"))
+ self.wait(secondsToMidnight(gmt=2), True)
+
+ if res == "error_wrong_captcha":
+ self.invalidCaptcha()
+ self.retry()
+
+ elif res.startswith('['):
+ urls = json_loads(res)
+
+ elif res.startswith('http://'):
+ urls = [res]
+
+ else:
+ self.error(_("Unknown response - captcha check"))
+
+ self.link = urls[0]
+
+
+ def handle_premium(self, pyfile):
+ api_key = self.user
+ premium_key = self.account.getAccountData(self.user)['password']
+
+ json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": 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'])
+
+ self.link = api_rep['data'][0][0]
diff --git a/pyload/plugin/hoster/LinksnappyCom.py b/pyload/plugin/hoster/LinksnappyCom.py
new file mode 100644
index 000000000..cd15301db
--- /dev/null
+++ b/pyload/plugin/hoster/LinksnappyCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class LinksnappyCom(MultiHoster):
+ __name = "LinksnappyCom"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'https?://(?:[^/]+\.)?linksnappy\.com'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Linksnappy.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ SINGLE_CHUNK_HOSTERS = ["easybytez.com"]
+
+
+ def handle_premium(self, pyfile):
+ 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.error(_("Error converting the link"))
+
+ pyfile.name = j['filename']
+ self.link = j['generated']
+
+ if host in self.SINGLE_CHUNK_HOSTERS:
+ self.chunkLimit = 1
+ else:
+ self.setup()
+
+
+ @staticmethod
+ def _get_host(url):
+ host = urlparse.urlsplit(url).netloc
+ return re.search(r'[\w-]+\.\w+$', host).group(0)
diff --git a/pyload/plugin/hoster/LoadTo.py b/pyload/plugin/hoster/LoadTo.py
new file mode 100644
index 000000000..1f9696a52
--- /dev/null
+++ b/pyload/plugin/hoster/LoadTo.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.load.to/JWydcofUY6/random.bin
+# http://www.load.to/oeSmrfkXE/random100.bin
+
+import re
+
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class LoadTo(SimpleHoster):
+ __name = "LoadTo"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?load\.to/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Load.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("halfman", "Pulpan3@gmail.com"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ NAME_PATTERN = r'<h1>(?P<N>.+)</h1>'
+ SIZE_PATTERN = r'Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>Can\'t find file'
+
+ LINK_FREE_PATTERN = r'<form method="post" action="(.+?)"'
+ WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
+
+ URL_REPLACEMENTS = [(r'(\w)$', r'\1/')]
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ # Search for Download URL
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = 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:
+ solvemedia = SolveMedia(self)
+ captcha_key = solvemedia.detect_key()
+
+ if captcha_key:
+ response, challenge = solvemedia.challenge(captcha_key)
+ self.download(self.link,
+ post={'adcopy_challenge': challenge,
+ 'adcopy_response' : response,
+ 'returnUrl' : pyfile.url})
diff --git a/pyload/plugin/hoster/LolabitsEs.py b/pyload/plugin/hoster/LolabitsEs.py
new file mode 100644
index 000000000..b98563bcf
--- /dev/null
+++ b/pyload/plugin/hoster/LolabitsEs.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*
+
+import HTMLParser
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class LolabitsEs(SimpleHoster):
+ __name = "LolabitsEs"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?lolabits\.es/.+'
+
+ __description = """Lolabits.es hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'Descargar: <b>(?P<N>.+?)<'
+ SIZE_PATTERN = r'class="fileSize">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'Un usuario con este nombre no existe'
+
+ FILEID_PATTERN = r'name="FileId" value="(\d+)"'
+ TOKEN_PATTERN = r'name="__RequestVerificationToken" type="hidden" value="(.+?)"'
+ LINK_PATTERN = r'"redirectUrl":"(.+?)"'
+
+
+ def setup(self):
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ fileid = re.search(self.FILEID_PATTERN, self.html).group(1)
+ self.logDebug("FileID: " + fileid)
+
+ token = re.search(self.TOKEN_PATTERN, self.html).group(1)
+ self.logDebug("Token: " + token)
+
+ self.html = self.load("http://lolabits.es/action/License/Download",
+ post={'fileId' : fileid,
+ '__RequestVerificationToken' : token}).decode('unicode-escape')
+
+ self.link = HTMLParser.HTMLParser().unescape(re.search(self.LINK_PATTERN, self.html).group(1))
diff --git a/pyload/plugin/hoster/LomafileCom.py b/pyload/plugin/hoster/LomafileCom.py
new file mode 100644
index 000000000..de56d2d58
--- /dev/null
+++ b/pyload/plugin/hoster/LomafileCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class LomafileCom(DeadHoster):
+ __name = "LomafileCom"
+ __type = "hoster"
+ __version = "0.52"
+
+ __pattern = r'http://lomafile\.com/\w{12}'
+ __config = []
+
+ __description = """Lomafile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("nath_schwarz", "nathan.notwhite@gmail.com"),
+ ("guidobelix", "guidobelix@hotmail.it")]
diff --git a/pyload/plugin/hoster/LuckyShareNet.py b/pyload/plugin/hoster/LuckyShareNet.py
new file mode 100644
index 000000000..d07500d93
--- /dev/null
+++ b/pyload/plugin/hoster/LuckyShareNet.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import json_loads
+
+
+class LuckyShareNet(SimpleHoster):
+ __name = "LuckyShareNet"
+ __type = "hoster"
+ __version = "0.06"
+
+ __pattern = r'https?://(?:www\.)?luckyshare\.net/(?P<ID>\d{10,})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """LuckyShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ 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'
+
+
+ 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:
+ seconds = int(m.group(1))
+ self.logDebug("You have to wait %d seconds between free downloads" % seconds)
+ self.retry(wait_time=seconds)
+ else:
+ self.error(_("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 handle_free(self, pyfile):
+ rep = self.load(r"http://luckyshare.net/download/request/type/time/file/" + self.info['pattern']['ID'], decode=True)
+
+ self.logDebug("JSON: " + rep)
+
+ json = self.parseJson(rep)
+ self.wait(json['time'])
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge()
+ 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.invalidCaptcha()
+ else:
+ self.error(_("Unable to get downlaod link"))
+
+ if not json['link']:
+ self.fail(_("No Download url retrieved/all captcha attempts failed"))
+
+ self.link = json['link']
diff --git a/pyload/plugin/hoster/MediafireCom.py b/pyload/plugin/hoster/MediafireCom.py
new file mode 100644
index 000000000..c3069d021
--- /dev/null
+++ b/pyload/plugin/hoster/MediafireCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MediafireCom(SimpleHoster):
+ __name = "MediafireCom"
+ __type = "hoster"
+ __version = "0.86"
+
+ __pattern = r'https?://(?:www\.)?mediafire\.com/(file/|view/\??|download(\.php\?|/)|\?)\w{15}'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Mediafire.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>.+?)"/>'
+ SIZE_PATTERN = r'<li>File size: <span>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ INFO_PATTERN = r'oFileSharePopup\.ald\(\'.*?\',\'(?P<N>.+?)\',\'(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)\',\'\',\'(?P<H>.+?)\'\)'
+ OFFLINE_PATTERN = r'class="error_msg_title"'
+
+ LINK_FREE_PATTERN = r'kNO = "(.+?)"'
+
+ PASSWORD_PATTERN = r'<form name="form_password"'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ solvemedia = SolveMedia(self)
+ captcha_key = solvemedia.detect_key()
+
+ if captcha_key:
+ response, challenge = solvemedia.challenge(captcha_key)
+ self.html = self.load(pyfile.url,
+ post={'adcopy_challenge': challenge,
+ 'adcopy_response' : response},
+ decode=True)
+
+ if self.PASSWORD_PATTERN in self.html:
+ password = self.getPassword()
+
+ if not password:
+ self.fail(_("No password found"))
+ else:
+ self.logInfo(_("Password protected link, trying: ") + password)
+ self.html = self.load(self.link, post={'downloadp': password})
+
+ if self.PASSWORD_PATTERN in self.html:
+ self.fail(_("Incorrect password"))
+
+ return super(MediafireCom, self).handle_free(pyfile)
diff --git a/pyload/plugin/hoster/MegaCoNz.py b/pyload/plugin/hoster/MegaCoNz.py
new file mode 100644
index 000000000..83409fde9
--- /dev/null
+++ b/pyload/plugin/hoster/MegaCoNz.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+
+import array
+import base64
+import os
+# import pycurl
+import random
+import re
+
+import Crypto
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import decode, fs_decode, fs_encode
+
+
+############################ General errors ###################################
+# EINTERNAL (-1): An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred
+# EARGS (-2): You have passed invalid arguments to this command
+# EAGAIN (-3): (always at the request level) A temporary congestion or server malfunction prevented your request from being processed. No data was altered. Retry. Retries must be spaced with exponential backoff
+# ERATELIMIT (-4): You have exceeded your command weight per time quota. Please wait a few seconds, then try again (this should never happen in sane real-life applications)
+#
+############################ Upload errors ####################################
+# EFAILED (-5): The upload failed. Please restart it from scratch
+# ETOOMANY (-6): Too many concurrent IP addresses are accessing this upload target URL
+# ERANGE (-7): The upload file packet is out of range or not starting and ending on a chunk boundary
+# EEXPIRED (-8): The upload target URL you are trying to access has expired. Please request a fresh one
+#
+############################ Stream/System errors #############################
+# ENOENT (-9): Object (typically, node or user) not found
+# ECIRCULAR (-10): Circular linkage attempted
+# EACCESS (-11): Access violation (e.g., trying to write to a read-only share)
+# EEXIST (-12): Trying to create an object that already exists
+# EINCOMPLETE (-13): Trying to access an incomplete resource
+# EKEY (-14): A decryption operation failed (never returned by the API)
+# ESID (-15): Invalid or expired user session, please relogin
+# EBLOCKED (-16): User blocked
+# EOVERQUOTA (-17): Request over quota
+# ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later
+# ETOOMANYCONNECTIONS (-19): Too many connections on this resource
+# EWRITE (-20): Write failed
+# EREAD (-21): Read failed
+# EAPPKEY (-22): Invalid application key; request not processed
+
+
+class MegaCoNz(Hoster):
+ __name = "MegaCoNz"
+ __type = "hoster"
+ __version = "0.26"
+
+ __pattern = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#(?P<TYPE>N|)!(?P<ID>[\w^_]+)!(?P<KEY>[\w,-]+)'
+
+ __description = """Mega.co.nz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "ranan@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_URL = "https://eu.api.mega.co.nz/cs"
+ FILE_SUFFIX = ".crypted"
+
+
+ def b64_decode(self, data):
+ data = data.replace("-", "+").replace("_", "/")
+ return base64.standard_b64decode(data + '=' * (-len(data) % 4))
+
+
+ def getCipherKey(self, key):
+ """ Construct the cipher key from the given data """
+ a = array.array("I", self.b64_decode(key))
+
+ k = array.array("I", (a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7]))
+ iv = a[4:6] + array.array("I", (0, 0))
+ meta_mac = a[6:8]
+
+ return k, iv, meta_mac
+
+
+ def api_response(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.random.randint(10 << 9, 10 ** 10)
+
+ res = self.load(self.API_URL, get={'id': uid}, post=json_dumps([kwargs]))
+ self.logDebug("Api Response: " + res)
+ return json_loads(res)
+
+
+ def decryptAttr(self, data, key):
+ k, iv, meta_mac = self.getCipherKey(key)
+ cbc = Crypto.Cipher.AES.new(k, Crypto.Cipher.AES.MODE_CBC, "\0" * 16)
+ attr = decode(cbc.decrypt(self.b64_decode(data)))
+
+ self.logDebug("Decrypted Attr: %s" % attr)
+ if not attr.startswith("MEGA"):
+ self.fail(_("Decryption failed"))
+
+ # Data is padded, 0-bytes must be stripped
+ return json_loads(re.search(r'{.+?}', attr).group(0))
+
+
+ def decryptFile(self, key):
+ """ Decrypts the file at lastDownload` """
+
+ # upper 64 bit of counter start
+ n = self.b64_decode(key)[16:24]
+
+ # convert counter to long and shift bytes
+ k, iv, meta_mac = self.getCipherKey(key)
+ ctr = Crypto.Util.Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64)
+ cipher = Crypto.Cipher.AES.new(k, Crypto.Cipher.AES.MODE_CTR, counter=ctr)
+
+ self.pyfile.setStatus("decrypting")
+ self.pyfile.setProgress(0)
+
+ file_crypted = fs_encode(self.lastDownload)
+ file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0]
+
+ try:
+ f = open(file_crypted, "rb")
+ df = open(file_decrypted, "wb")
+
+ except IOError, e:
+ self.fail(e)
+
+ chunk_size = 2 ** 15 #: buffer size, 32k
+ # file_mac = [0, 0, 0, 0] #: calculate CBC-MAC for checksum
+
+ chunks = os.path.getsize(file_crypted) / chunk_size + 1
+ for i in xrange(chunks):
+ buf = f.read(chunk_size)
+ if not buf:
+ break
+
+ chunk = cipher.decrypt(buf)
+ df.write(chunk)
+
+ self.pyfile.setProgress(int((100.0 / chunks) * i))
+
+ # chunk_mac = [iv[0], iv[1], iv[0], iv[1]]
+ # for i in xrange(0, chunk_size, 16):
+ # block = chunk[i:i+16]
+ # if len(block) % 16:
+ # block += '=' * (16 - (len(block) % 16))
+ # block = array.array("I", block)
+
+ # chunk_mac = [chunk_mac[0] ^ a_[0], chunk_mac[1] ^ block[1], chunk_mac[2] ^ block[2], chunk_mac[3] ^ block[3]]
+ # chunk_mac = aes_cbc_encrypt_a32(chunk_mac, k)
+
+ # file_mac = [file_mac[0] ^ chunk_mac[0], file_mac[1] ^ chunk_mac[1], file_mac[2] ^ chunk_mac[2], file_mac[3] ^ chunk_mac[3]]
+ # file_mac = aes_cbc_encrypt_a32(file_mac, k)
+
+ self.pyfile.setProgress(100)
+
+ f.close()
+ df.close()
+
+ # if file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3] != meta_mac:
+ # os.remove(file_decrypted)
+ # self.fail(_("Checksum mismatch"))
+
+ os.remove(file_crypted)
+ self.lastDownload = fs_decode(file_decrypted)
+
+
+ def checkError(self, code):
+ ecode = abs(code)
+
+ if ecode in (9, 16, 21):
+ self.offline()
+
+ elif ecode in (3, 13, 17, 18, 19):
+ self.tempOffline()
+
+ elif ecode in (1, 4, 6, 10, 15, 21):
+ self.retry(5, 30, _("Error code: [%s]") % -ecode)
+
+ else:
+ self.fail(_("Error code: [%s]") % -ecode)
+
+
+ def process(self, pyfile):
+ pattern = re.match(self.__pattern, pyfile.url).groupdict()
+ id = pattern['ID']
+ key = pattern['KEY']
+ public = pattern['TYPE'] == ''
+
+ self.logDebug("ID: %s" % id, "Key: %s" % key, "Type: %s" % ("public" if public else "node"))
+
+ # g is for requesting a download url
+ # this is similar to the calls in the mega js app, documentation is very bad
+ if public:
+ mega = self.api_response(a="g", g=1, p=id, ssl=1)[0]
+ else:
+ mega = self.api_response(a="g", g=1, n=id, ssl=1)[0]
+
+ if isinstance(mega, int):
+ self.checkError(mega)
+ elif "e" in mega:
+ self.checkError(mega['e'])
+
+ attr = self.decryptAttr(mega['at'], key)
+
+ pyfile.name = attr['n'] + self.FILE_SUFFIX
+ pyfile.size = mega['s']
+
+ # self.req.http.c.setopt(pycurl.SSL_CIPHER_LIST, "RC4-MD5:DEFAULT")
+
+ self.download(mega['g'])
+
+ self.decryptFile(key)
+
+ # Everything is finished and final name can be set
+ pyfile.name = attr['n']
diff --git a/pyload/plugin/hoster/MegaDebridEu.py b/pyload/plugin/hoster/MegaDebridEu.py
new file mode 100644
index 000000000..4eeb07d1d
--- /dev/null
+++ b/pyload/plugin/hoster/MegaDebridEu.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class MegaDebridEu(MultiHoster):
+ __name = "MegaDebridEu"
+ __type = "hoster"
+ __version = "0.47"
+
+ __pattern = r'http://((?:www\d+\.|s\d+\.)?mega-debrid\.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[\w^_]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """mega-debrid.eu multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("D.Ducatel", "dducatel@je-geek.fr")]
+
+
+ API_URL = "https://www.mega-debrid.eu/api.php"
+
+
+ def api_load(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']})
+ res = json_loads(jsonResponse)
+
+ if res['response_code'] == "ok":
+ self.token = res['token']
+ return True
+ else:
+ return False
+
+
+ def handle_premium(self, pyfile):
+ """
+ Debrid a link
+ Return The debrided link if succeed or original link if fail
+ """
+ if not self.api_load():
+ self.error("Unable to connect to remote API")
+
+ jsonResponse = self.load(self.API_URL,
+ get={'action': 'getLink', 'token': self.token},
+ post={'link': pyfile.url})
+
+ res = json_loads(jsonResponse)
+ if res['response_code'] == "ok":
+ self.link = res['debridLink'][1:-1]
diff --git a/pyload/plugin/hoster/MegaFilesSe.py b/pyload/plugin/hoster/MegaFilesSe.py
new file mode 100644
index 000000000..51667c786
--- /dev/null
+++ b/pyload/plugin/hoster/MegaFilesSe.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegaFilesSe(DeadHoster):
+ __name = "MegaFilesSe"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?megafiles\.se/\w{12}'
+ __config = []
+
+ __description = """MegaFiles.se hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/MegaRapidCz.py b/pyload/plugin/hoster/MegaRapidCz.py
new file mode 100644
index 000000000..41e6e7412
--- /dev/null
+++ b/pyload/plugin/hoster/MegaRapidCz.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getRequest
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ h = getRequest()
+ h.c.setopt(pycurl.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)
+ yield parseFileInfo(MegaRapidCz, url, html)
+
+
+class MegaRapidCz(SimpleHoster):
+ __name = "MegaRapidCz"
+ __type = "hoster"
+ __version = "0.56"
+
+ __pattern = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """MegaRapid.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("MikyWoW", "mikywow@seznam.cz"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<h1.*?><span.*?>(?:<a.*?>)?(?P<N>[^<]+)'
+ SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong></td>'
+ OFFLINE_PATTERN = ur'Nastala chyba 404|Soubor byl smazán'
+
+ CHECK_TRAFFIC = True
+
+ LINK_PREMIUM_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 handle_premium(self, pyfile):
+ m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+ else:
+ if re.search(self.ERR_LOGIN_PATTERN, self.html):
+ self.relogin(self.user)
+ self.retry(wait_time=60, 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/plugin/hoster/MegaRapidoNet.py b/pyload/plugin/hoster/MegaRapidoNet.py
new file mode 100644
index 000000000..484f8e72f
--- /dev/null
+++ b/pyload/plugin/hoster/MegaRapidoNet.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import random
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+def random_with_N_digits(n):
+ rand = "0."
+ not_zero = 0
+ for _i in xrange(1, n + 1):
+ r = random.randint(0, 9)
+ if(r > 0):
+ not_zero += 1
+ rand += str(r)
+
+ if not_zero > 0:
+ return rand
+ else:
+ return random_with_N_digits(n)
+
+
+class MegaRapidoNet(MultiHoster):
+ __name = "MegaRapidoNet"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?\w+\.megarapido\.net/\?file=\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """MegaRapido.net multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ LINK_PREMIUM_PATTERN = r'<\s*?a[^>]*?title\s*?=\s*?["\'].*?download["\'][^>]*?href=["\']([^"\']+)'
+
+ ERROR_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']?alert-message error.*?>([^<]*)'
+
+
+ def handle_premium(self, pyfile):
+ self.html = self.load("http://megarapido.net/gerar.php",
+ post={'rand' :random_with_N_digits(16),
+ 'urllist' : pyfile.url,
+ 'links' : pyfile.url,
+ 'exibir' : "normal",
+ 'usar' : "premium",
+ 'user' : self.account.getAccountInfo(self.user).get('sid', None),
+ 'autoreset': ""})
+
+ if "desloga e loga novamente para gerar seus links" in self.html.lower():
+ self.error("You have logged in at another place")
+
+ return super(MegaRapidoNet, self).handle_premium(pyfile)
diff --git a/pyload/plugin/hoster/MegacrypterCom.py b/pyload/plugin/hoster/MegacrypterCom.py
new file mode 100644
index 000000000..264ad958d
--- /dev/null
+++ b/pyload/plugin/hoster/MegacrypterCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads, json_dumps
+
+from pyload.plugin.hoster.MegaCoNz import MegaCoNz
+
+
+class MegacrypterCom(MegaCoNz):
+ __name = "MegacrypterCom"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'https?://\w{0,10}\.?megacrypter\.com/[\w!-]+'
+
+ __description = """Megacrypter.com decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("GonzaloSR", "gonzalo@gonzalosr.com")]
+
+
+ API_URL = "http://megacrypter.com/api"
+ FILE_SUFFIX = ".crypted"
+
+
+ def api_response(self, **kwargs):
+ """ Dispatch a call to the api, see megacrypter.com/api_doc """
+ self.logDebug("JSON request: " + json_dumps(kwargs))
+ res = self.load(self.API_URL, post=json_dumps(kwargs))
+ self.logDebug("API Response: " + res)
+ return json_loads(res)
+
+
+ def process(self, pyfile):
+ # match is guaranteed because plugin was chosen to handle url
+ node = re.match(self.__pattern, pyfile.url).group(0)
+
+ # get Mega.co.nz link info
+ info = self.api_response(link=node, m="info")
+
+ # get crypted file URL
+ dl = self.api_response(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/plugin/hoster/MegareleaseOrg.py b/pyload/plugin/hoster/MegareleaseOrg.py
new file mode 100644
index 000000000..65f1242a9
--- /dev/null
+++ b/pyload/plugin/hoster/MegareleaseOrg.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegareleaseOrg(DeadHoster):
+ __name = "MegareleaseOrg"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)?megarelease\.org/\w{12}'
+ __config = []
+
+ __description = """Megarelease.org hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("derek3x", "derek3x@vmail.me"),
+ ("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/MegasharesCom.py b/pyload/plugin/hoster/MegasharesCom.py
new file mode 100644
index 000000000..11010b63e
--- /dev/null
+++ b/pyload/plugin/hoster/MegasharesCom.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MegasharesCom(SimpleHoster):
+ __name = "MegasharesCom"
+ __type = "hoster"
+ __version = "0.28"
+
+ __pattern = r'http://(?:www\.)?(d\d{2}\.)?megashares\.com/((index\.php)?\?d\d{2}=|dl/)\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Megashares.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<h1 class="black xxl"[^>]*title="(?P<N>.+?)">'
+ SIZE_PATTERN = r'<strong><span class="black">Filesize:</span></strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'<dd class="red">(Invalid Link Request|Link has been deleted|Invalid link)'
+
+ LINK_PATTERN = r'<div id="show_download_button_%d".*?>\s*<a href="(.+?)">'
+
+ PASSPORT_LEFT_PATTERN = r'Your Download Passport is: <.*?>(\w+).*?You have.*?<.*?>.*?([\d.]+) (\w+)'
+ PASSPORT_RENEW_PATTERN = r'(\d+):<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 handle_premium(self, pyfile):
+ self.handleDownload(True)
+
+
+ def handle_free(self, pyfile):
+ if self.NO_SLOTS_PATTERN in self.html:
+ self.retry(wait_time=5 * 60)
+
+ 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 _i in xrange(5):
+ random_num = re.search(self.REACTIVATE_NUM_PATTERN, self.html).group(1)
+
+ verifyinput = self.decryptCaptcha("http://d01.megashares.com/index.php",
+ get={'secgfx': "gfx", 'random_num': random_num})
+
+ self.logInfo(_("Reactivating passport %s: %s %s") % (passport_num, random_num, verifyinput))
+
+ res = self.load("http://d01.megashares.com%s" % request_uri,
+ get={'rs' : "check_passport_renewal",
+ 'rsargs[]': verifyinput,
+ 'rsargs[]': random_num,
+ 'rsargs[]': passport_num,
+ 'rsargs[]': "replace_sec_pprenewal",
+ 'rsrnd[]' : str(int(time.time() * 1000))})
+
+ if 'Thank you for reactivating your passport.' in res:
+ self.correctCaptcha()
+ self.retry()
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail(_("Failed to reactivate passport"))
+
+ m = re.search(self.PASSPORT_RENEW_PATTERN, self.html)
+ if m:
+ time = [int(x) for x in m.groups()]
+ renew = time[0] + (time[1] * 60) + (time[2] * 60)
+ self.logDebug("Waiting %d seconds for a new passport" % renew)
+ self.retry(wait_time=renew, reason=_("Passport renewal"))
+
+ # Check traffic left on passport
+ m = re.search(self.PASSPORT_LEFT_PATTERN, self.html, re.M | re.S)
+ if m is None:
+ self.fail(_("Passport not found"))
+
+ self.logInfo(_("Download passport: %s") % m.group(1))
+ data_left = float(m.group(2)) * (2 ** 20) ** {'B': 0, '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:
+ self.retry(wait_time=600, reason=_("Passport renewal"))
+
+ 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.error(msg)
+
+ self.link = m.group(1)
+ self.logDebug("%s: %s" % (msg, self.link))
diff --git a/pyload/plugin/hoster/MegauploadCom.py b/pyload/plugin/hoster/MegauploadCom.py
new file mode 100644
index 000000000..20acad9a6
--- /dev/null
+++ b/pyload/plugin/hoster/MegauploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegauploadCom(DeadHoster):
+ __name = "MegauploadCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'http://(?:www\.)?megaupload\.com/\?.*&?(d|v)=\w+'
+ __config = []
+
+ __description = """Megaupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org")]
diff --git a/pyload/plugin/hoster/MegavideoCom.py b/pyload/plugin/hoster/MegavideoCom.py
new file mode 100644
index 000000000..f50ce4365
--- /dev/null
+++ b/pyload/plugin/hoster/MegavideoCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class MegavideoCom(DeadHoster):
+ __name = "MegavideoCom"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?megavideo\.com/\?.*&?(d|v)=\w+'
+ __config = []
+
+ __description = """Megavideo.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/hoster/MovReelCom.py b/pyload/plugin/hoster/MovReelCom.py
new file mode 100644
index 000000000..fb942c59f
--- /dev/null
+++ b/pyload/plugin/hoster/MovReelCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class MovReelCom(XFSHoster):
+ __name = "MovReelCom"
+ __type = "hoster"
+ __version = "1.24"
+
+ __pattern = r'http://(?:www\.)?movreel\.com/\w{12}'
+
+ __description = """MovReel.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("JorisV83", "jorisv83-pyload@yahoo.com")]
+
+
+ LINK_PATTERN = r'<a href="(.+?)">Download Link'
diff --git a/pyload/plugin/hoster/MultihostersCom.py b/pyload/plugin/hoster/MultihostersCom.py
new file mode 100644
index 000000000..1bb2452bb
--- /dev/null
+++ b/pyload/plugin/hoster/MultihostersCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.ZeveraCom import ZeveraCom
+
+
+class MultihostersCom(ZeveraCom):
+ __name = "MultihostersCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)multihosters\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
+
+ __description = """Multihosters.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("tjeh", "tjeh@gmx.net")]
diff --git a/pyload/plugin/hoster/MultishareCz.py b/pyload/plugin/hoster/MultishareCz.py
new file mode 100644
index 000000000..ade049f50
--- /dev/null
+++ b/pyload/plugin/hoster/MultishareCz.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import random
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MultishareCz(SimpleHoster):
+ __name = "MultishareCz"
+ __type = "hoster"
+ __version = "0.40"
+
+ __pattern = r'http://(?:www\.)?multishare\.cz/stahnout/(?P<ID>\d+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """MultiShare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ SIZE_REPLACEMENTS = [('&nbsp;', '')]
+
+ CHECK_TRAFFIC = True
+ MULTI_HOSTER = True
+
+ 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>'
+
+
+ def handle_free(self, pyfile):
+ self.download("http://www.multishare.cz/html/download_free.php", get={'ID': self.info['pattern']['ID']})
+
+
+ def handle_premium(self, pyfile):
+ self.download("http://www.multishare.cz/html/download_premium.php", get={'ID': self.info['pattern']['ID']})
+
+
+ def handle_multi(self, pyfile):
+ self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": pyfile.url}, decode=True)
+
+ self.checkInfo()
+
+ if not self.checkTrafficLeft():
+ self.fail(_("Not enough credit left to download file"))
+
+ self.download("http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random.random() * 10000 * random.random()),
+ get={'u_ID' : self.acc_info['u_ID'],
+ 'u_hash': self.acc_info['u_hash'],
+ 'link' : pyfile.url},
+ disposition=True)
diff --git a/pyload/plugin/hoster/MyfastfileCom.py b/pyload/plugin/hoster/MyfastfileCom.py
new file mode 100644
index 000000000..4ed89f8f7
--- /dev/null
+++ b/pyload/plugin/hoster/MyfastfileCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class MyfastfileCom(MultiHoster):
+ __name = "MyfastfileCom"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Myfastfile.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+
+
+ def handle_premium(self, pyfile):
+ self.html = self.load('http://myfastfile.com/api.php',
+ get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'],
+ 'link': pyfile.url})
+ self.logDebug("JSON data: " + self.html)
+
+ self.html = json_loads(self.html)
+ if self.html['status'] != 'ok':
+ self.fail(_("Unable to unrestrict link"))
+
+ self.link = self.html['link']
diff --git a/pyload/plugin/hoster/MystoreTo.py b/pyload/plugin/hoster/MystoreTo.py
new file mode 100644
index 000000000..1518dd75f
--- /dev/null
+++ b/pyload/plugin/hoster/MystoreTo.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+#
+# Test link:
+# http://mystore.to/dl/mxcA50jKfP
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class MystoreTo(SimpleHoster):
+ __name = "MystoreTo"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?mystore\.to/dl/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Mystore.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "")]
+
+
+ NAME_PATTERN = r'<h1>(?P<N>.+?)<'
+ SIZE_PATTERN = r'FILESIZE: (?P<S>[\d\.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>file not found<'
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ try:
+ fid = re.search(r'wert="(.+?)"', self.html).group(1)
+
+ except AttributeError:
+ self.error(_("File-ID not found"))
+
+ self.link = self.load("http://mystore.to/api/download",
+ post={'FID': fid})
diff --git a/pyload/plugin/hoster/MyvideoDe.py b/pyload/plugin/hoster/MyvideoDe.py
new file mode 100644
index 000000000..08652cdc9
--- /dev/null
+++ b/pyload/plugin/hoster/MyvideoDe.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import html_unescape
+
+
+class MyvideoDe(Hoster):
+ __name = "MyvideoDe"
+ __type = "hoster"
+ __version = "0.90"
+
+ __pattern = r'http://(?:www\.)?myvideo\.de/watch/'
+
+ __description = """Myvideo.de hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "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/plugin/hoster/NahrajCz.py b/pyload/plugin/hoster/NahrajCz.py
new file mode 100644
index 000000000..14f041e17
--- /dev/null
+++ b/pyload/plugin/hoster/NahrajCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class NahrajCz(DeadHoster):
+ __name = "NahrajCz"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'http://(?:www\.)?nahraj\.cz/content/download/.+'
+ __config = []
+
+ __description = """Nahraj.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/NarodRu.py b/pyload/plugin/hoster/NarodRu.py
new file mode 100644
index 000000000..38c867304
--- /dev/null
+++ b/pyload/plugin/hoster/NarodRu.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class NarodRu(SimpleHoster):
+ __name = "NarodRu"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/\d+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Narod.ru hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<dt class="name">(?:<[^<]*>)*(?P<N>[^<]+)</dt>'
+ SIZE_PATTERN = r'<dd class="size">(?P<S>\d[^<]*)</dd>'
+ OFFLINE_PATTERN = r'<title>404</title>|Ѐайл уЎалеМ с сервОса|ЗакПМчОлся срПк храМеМОя файла\.'
+
+ SIZE_REPLACEMENTS = [(u'КБ', 'KB'), (u'МБ', 'MB'), (u'ГБ', 'GB')]
+ URL_REPLACEMENTS = [("narod.yandex.ru/", "narod.ru/"),
+ (r"/start/\d+\.\w+-narod\.yandex\.ru/(\d{6,15})/\w+/(\w+)", r"/disk/\1/\2")]
+
+ CAPTCHA_PATTERN = r'<number url="(.*?)">(\w+)</number>'
+ LINK_FREE_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">'
+
+
+ def handle_free(self, pyfile):
+ for _i in xrange(5):
+ self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(random.random() * 777))
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("Captcha"))
+
+ post_data = {"action": "sendcapcha"}
+ captcha_url, post_data['key'] = m.groups()
+ post_data['rep'] = self.decryptCaptcha(captcha_url)
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = 'http://narod.ru' + m.group(1)
+ self.correctCaptcha()
+ break
+
+ elif u'<b class="error-msg"><strong>ОшОблОсь?</strong>' in self.html:
+ self.invalidCaptcha()
+
+ else:
+ self.error(_("Download link"))
+
+ else:
+ self.fail(_("No valid captcha code entered"))
diff --git a/pyload/plugin/hoster/NetloadIn.py b/pyload/plugin/hoster/NetloadIn.py
new file mode 100644
index 000000000..9e05b02c5
--- /dev/null
+++ b/pyload/plugin/hoster/NetloadIn.py
@@ -0,0 +1,297 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import chunks
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+def getInfo(urls):
+ ## returns list of tupels (name, size (in bytes), status (see database.File), url)
+
+ apiurl = "http://api.netload.in/info.php"
+ 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('ID') + ";"
+
+ api = getURL(apiurl,
+ get={'auth' : "Zf9SnQh9WiReEsb18akjvQGqT0I830e8",
+ 'bz' : 1,
+ 'md5' : 1,
+ 'file_id': ids},
+ decode=True)
+
+ if api is None or len(api) < 10:
+ self.logDebug("Prefetch failed")
+ return
+
+ if api.find("unknown_auth") >= 0:
+ self.logDebug("Outdated auth code")
+ return
+
+ result = []
+
+ for i, r in enumerate(api.splitlines()):
+ try:
+ tmp = r.split(";")
+
+ try:
+ size = int(tmp[2])
+ except Exception:
+ size = 0
+
+ result.append((tmp[1], size, 2 if tmp[3] == "online" else 1, chunk[i] ))
+
+ except Exception:
+ self.logDebug("Error while processing response: %s" % r)
+
+ yield result
+
+
+class NetloadIn(Hoster):
+ __name = "NetloadIn"
+ __type = "hoster"
+ __version = "0.49"
+
+ __pattern = r'https?://(?:www\.)?netload\.in/(?P<PATH>datei|index\.php\?id=10&file_id=)(?P<ID>\w+)'
+
+ __description = """Netload.in hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("RaNaN", "ranan@pyload.org"),
+ ("Gregy", "gregy@gregy.cz")]
+
+
+ RECAPTCHA_KEY = "6LcLJMQSAAAAAJzquPUPKNovIhbK6LpSqCjYrsR1"
+
+
+ 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.api_load()
+
+ if self.api_data and self.api_data['filename']:
+ self.pyfile.name = self.api_data['filename']
+
+ if self.premium:
+ self.logDebug("Use Premium Account")
+
+ settings = self.load("http://www.netload.in/index.php", get={'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 api_load(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('ID')
+ self.logDebug("URL: %s" % self.url)
+ else:
+ self.api_data = False
+ return
+
+ apiurl = "http://api.netload.in/info.php"
+ html = self.load(apiurl, cookies=False,
+ get={"file_id": match.group('ID'), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1",
+ "md5": "1"}, decode=True).strip()
+ if not html and n <= 3:
+ self.setWait(2)
+ self.wait()
+ self.api_load(n + 1)
+ return
+
+ self.logDebug("APIDATA: " + html)
+
+ self.api_data = {}
+
+ if html and ";" in html and html not in ("unknown file_data", "unknown_server_data", "No input file specified."):
+ lines = html.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.time(page)
+
+ self.setWait(wait_time)
+
+ self.logDebug("Final wait %d seconds" % wait_time)
+
+ self.wait()
+
+ self.url = self.get_file_url(page)
+
+
+ def check_free_wait(self, page):
+ if ">An access request has been made from IP address <" in page:
+ self.wantReconnect = True
+ self.setWait(self.get_wait_time.time(page) or 30)
+ self.wait()
+ return True
+ else:
+ return False
+
+
+ def download_html(self):
+ page = self.load(self.url, decode=True)
+
+ 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.M)
+ # 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(5):
+ if not page:
+ page = self.load(self.url)
+ t = time.time() + 30
+
+ if "/share/templates/download_hddcrash.tpl" in page:
+ self.logError(_("Netload HDD Crash"))
+ self.fail(_("File temporarily not available"))
+
+ self.logDebug("Try number %d " % i)
+
+ if ">Your download is being prepared.<" in page:
+ self.logDebug("We will prepare your download")
+ self.final_wait(page)
+ return True
+
+ self.logDebug("Trying to find captcha")
+
+ try:
+ url_captcha_html = re.search(r'(index.php\?id=10&amp;.*&amp;captcha=1)', page).group(1).replace("amp;", "")
+
+ except Exception, e:
+ self.logDebug("Exception during Captcha regex: %s" % e.message)
+ page = None
+
+ else:
+ url_captcha_html = urlparse.urljoin("http://netload.in/", url_captcha_html)
+ break
+
+ self.html = self.load(url_captcha_html)
+
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ response_page = self.load("http://www.netload.in/index.php?id=10",
+ post={'captcha_check' : '1',
+ 'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response,
+ 'file_id' : self.api_data['fileid'],
+ 'Download_Next' : ''})
+ if "Orange_Link" in response_page:
+ break
+
+ if self.check_free_wait(response_page):
+ self.logDebug("Had to wait for next free slot, trying again")
+ return self.download_html()
+
+ else:
+ download_url = self.get_file_url(response_page)
+ self.logDebug("Download URL after get_file: " + download_url)
+ if not download_url.startswith("http://"):
+ self.error(_("Download url: %s") % download_url)
+ self.wait()
+
+ self.url = download_url
+ return True
+
+
+ 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:
+ return attempt.group(1)
+ else:
+ self.logDebug("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 Exception, e:
+ self.logDebug("Getting final link failed", e.message)
+ return None
+
+
+ def get_wait_time.time(self, page):
+ return int(re.search(r"countdown\((.+),'change\(\)'\)", page).group(1)) / 100
+
+
+ def proceed(self, url):
+ 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/plugin/hoster/NitroflareCom.py b/pyload/plugin/hoster/NitroflareCom.py
new file mode 100644
index 000000000..aed0b404f
--- /dev/null
+++ b/pyload/plugin/hoster/NitroflareCom.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class NitroflareCom(SimpleHoster):
+ __name = "NitroflareCom"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'https?://(?:www\.)?nitroflare\.com/view/(?P<ID>[\w^_]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Nitroflare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sahil", "sahilshekhawat01@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("Stickell", "l.stickell@yahoo.it")]
+
+ # URL_REPLACEMENTS = [("http://", "https://")]
+
+ INFO_PATTERN = r'title="(?P<N>.+?)".+>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'>File doesn\'t exist'
+
+ LINK_FREE_PATTERN = r'(https?://[\w\-]+\.nitroflare\.com/.+?)"'
+
+ RECAPTCHA_KEY = "6Lenx_USAAAAAF5L1pmTWvWcH73dipAEzNnmNLgy"
+
+ PREMIUM_ONLY_PATTERN = r'This file is available with Premium only'
+ WAIT_PATTERN = r'You have to wait .+?<'
+ ERROR_PATTERN = r'downloading is not possible'
+
+
+ def checkErrors(self):
+ if not self.html:
+ return
+
+ if not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
+ self.fail(_("Link require a premium account to be handled"))
+
+ elif hasattr(self, 'WAIT_PATTERN'):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1}[u.lower()] for v, u in
+ re.findall(r'(\d+)\s*(hr|hour|min|sec)', m.group(0), re.I))
+ self.wait(wait_time, wait_time > 300)
+ return
+
+ elif hasattr(self, 'ERROR_PATTERN'):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ errmsg = self.info['error'] = m.group(1)
+ self.error(errmsg)
+
+ self.info.pop('error', None)
+
+
+ def handle_free(self, pyfile):
+ # used here to load the cookies which will be required later
+ self.load(pyfile.url, post={'goToFreePage': ""})
+
+ self.load("http://nitroflare.com/ajax/setCookie.php", post={'fileId': self.info['pattern']['ID']})
+ self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
+ post={'method': "startTimer", 'fileId': self.info['pattern']['ID']})
+
+ self.checkErrors()
+
+ try:
+ js_file = self.load("http://nitroflare.com/js/downloadFree.js?v=1.0.1")
+ var_time = re.search("var time = (\\d+);", js_file)
+ wait_time = int(var_time.groups()[0])
+
+ except Exception:
+ wait_time = 60
+
+ self.wait(wait_time)
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ self.html = self.load("http://nitroflare.com/ajax/freeDownload.php",
+ post={'method' : "fetchDownload",
+ 'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ if "The captcha wasn't entered correctly" in self.html:
+ self.logWarning("The captcha wasn't entered correctly")
+ return
+
+ if "You have to fill the captcha" in self.html:
+ self.logWarning("Captcha unfilled")
+ return
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+ else:
+ self.logError("Unable to detect direct link")
diff --git a/pyload/plugin/hoster/NoPremiumPl.py b/pyload/plugin/hoster/NoPremiumPl.py
new file mode 100644
index 000000000..4714270b5
--- /dev/null
+++ b/pyload/plugin/hoster/NoPremiumPl.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class NoPremiumPl(MultiHoster):
+ __name = "NoPremiumPl"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://direct\.nopremium\.pl.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """NoPremium.pl multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@nopremium.pl")]
+
+
+ API_URL = "http://crypt.nopremium.pl"
+
+ API_QUERY = {'site' : "nopremium",
+ 'output' : "json",
+ 'username': "",
+ 'password': "",
+ 'url' : ""}
+
+ ERROR_CODES = {0 : "[%s] Incorrect login credentials",
+ 1 : "[%s] Not enough transfer to download - top-up your account",
+ 2 : "[%s] Incorrect / dead link",
+ 3 : "[%s] Error connecting to hosting, try again later",
+ 9 : "[%s] Premium account has expired",
+ 15: "[%s] Hosting no longer supported",
+ 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
+
+
+ def prepare(self):
+ super(NoPremiumPl, self).prepare()
+
+ data = self.account.getAccountData(self.user)
+
+ self.usr = data['usr']
+ self.pwd = data['pwd']
+
+
+ def runFileQuery(self, url, mode=None):
+ query = self.API_QUERY.copy()
+
+ query['username'] = self.usr
+ query['password'] = self.pwd
+ query['url'] = url
+
+ if mode == "fileinfo":
+ query['check'] = 2
+ query['loc'] = 1
+
+ self.logDebug(query)
+
+ return self.load(self.API_URL, post=query)
+
+
+ def handle_free(self, pyfile):
+ try:
+ data = self.runFileQuery(pyfile.url, 'fileinfo')
+
+ except Exception:
+ self.logDebug("runFileQuery error")
+ self.tempOffline()
+
+ try:
+ parsed = json_loads(data)
+
+ except Exception:
+ self.logDebug("loads error")
+ self.tempOffline()
+
+ self.logDebug(parsed)
+
+ if "errno" in parsed.keys():
+ if parsed['errno'] in self.ERROR_CODES:
+ # error code in known
+ self.fail(self.ERROR_CODES[parsed['errno']] % self.getClassName())
+ else:
+ # error code isn't yet added to plugin
+ self.fail(
+ parsed['errstring']
+ or _("Unknown error (code: %s)") % parsed['errno']
+ )
+
+ if "sdownload" in parsed:
+ if parsed['sdownload'] == "1":
+ self.fail(
+ _("Download from %s is possible only using NoPremium.pl website \
+ directly") % parsed['hosting'])
+
+ pyfile.name = parsed['filename']
+ pyfile.size = parsed['filesize']
+
+ try:
+ self.link = self.runFileQuery(pyfile.url, 'filedownload')
+
+ except Exception:
+ self.logDebug("runFileQuery error #2")
+ self.tempOffline()
diff --git a/pyload/plugin/hoster/NosuploadCom.py b/pyload/plugin/hoster/NosuploadCom.py
new file mode 100644
index 000000000..af79da22b
--- /dev/null
+++ b/pyload/plugin/hoster/NosuploadCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class NosuploadCom(XFSHoster):
+ __name = "NosuploadCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'http://(?:www\.)?nosupload\.com/\?d=\w{12}'
+
+ __description = """Nosupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ SIZE_PATTERN = r'<p><strong>Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)</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, 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.M | re.S).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, decode=True)
+
+ # stage3: get the download link
+ return re.search(self.LINK_PATTERN, self.html, re.S).group(1)
diff --git a/pyload/plugin/hoster/NovafileCom.py b/pyload/plugin/hoster/NovafileCom.py
new file mode 100644
index 000000000..f76d77269
--- /dev/null
+++ b/pyload/plugin/hoster/NovafileCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://novafile.com/vfun4z6o2cit
+# http://novafile.com/s6zrr5wemuz4
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class NovafileCom(XFSHoster):
+ __name = "NovafileCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?novafile\.com/\w{12}'
+
+ __description = """Novafile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ ERROR_PATTERN = r'class="alert.+?alert-separate".*?>\s*(?:<p>)?(.*?)\s*</'
+ WAIT_PATTERN = r'<p>Please wait <span id="count".*?>(\d+)</span> seconds</p>'
+
+ LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>'
diff --git a/pyload/plugin/hoster/NowDownloadSx.py b/pyload/plugin/hoster/NowDownloadSx.py
new file mode 100644
index 000000000..b0c8d60e2
--- /dev/null
+++ b/pyload/plugin/hoster/NowDownloadSx.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import fixup
+
+
+class NowDownloadSx(SimpleHoster):
+ __name = "NowDownloadSx"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'http://(?:www\.)?(nowdownload\.[a-zA-Z]{2,}/(dl/|download\.php.+?id=|mobile/(#/files/|.+?id=))|likeupload\.org/)\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """NowDownload.sx hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("godofdream", "soilfiction@gmail.com"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ INFO_PATTERN = r'Downloading</span> <br> (?P<N>.*) (?P<S>[\d.,]+) (?P<U>[\w^_]+) </h4>'
+ OFFLINE_PATTERN = r'>This file does not exist'
+
+ TOKEN_PATTERN = r'"(/api/token\.php\?token=\w+)"'
+ CONTINUE_PATTERN = r'"(/dl2/\w+/\w+)"'
+ WAIT_PATTERN = r'\.countdown\(\{until: \+(\d+),'
+ LINK_FREE_PATTERN = r'(http://s\d+\.coolcdn\.info/nowdownload/.+?)["\']'
+
+ NAME_REPLACEMENTS = [("&#?\w+;", fixup), (r'<.*?>', '')]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = -1
+
+
+ def handle_free(self, pyfile):
+ 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.error()
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait = int(m.group(1))
+ else:
+ wait = 60
+
+ baseurl = "http://www.nowdownload.at"
+ self.html = self.load(baseurl + str(tokenlink.group(1)))
+ self.wait(wait)
+
+ self.html = self.load(baseurl + str(continuelink.group(1)))
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Download link not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/NowVideoSx.py b/pyload/plugin/hoster/NowVideoSx.py
new file mode 100644
index 000000000..70dc1fdf5
--- /dev/null
+++ b/pyload/plugin/hoster/NowVideoSx.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class NowVideoSx(SimpleHoster):
+ __name = "NowVideoSx"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?nowvideo\.[a-zA-Z]{2,}/(video/|mobile/(#/videos/|.+?id=))(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """NowVideo.sx hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.nowvideo.sx/video/\g<ID>')]
+
+ NAME_PATTERN = r'<h4>(?P<N>.+?)<'
+ OFFLINE_PATTERN = r'>This file no longer exists'
+
+ LINK_FREE_PATTERN = r'<source src="(.+?)"'
+ LINK_PREMIUM_PATTERN = r'<div id="content_player" >\s*<a href="(.+?)"'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ self.html = self.load("http://www.nowvideo.sx/mobile/video.php", get={'id': self.info['pattern']['ID']})
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/OboomCom.py b/pyload/plugin/hoster/OboomCom.py
new file mode 100644
index 000000000..5b9b11485
--- /dev/null
+++ b/pyload/plugin/hoster/OboomCom.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# https://www.oboom.com/B7CYZIEB/10Mio.dat
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+
+
+class OboomCom(Hoster):
+ __name = "OboomCom"
+ __type = "hoster"
+ __version = "0.31"
+
+ __pattern = r'https?://(?:www\.)?oboom\.com/(#(id=|/)?)?(?P<ID>\w{8})'
+
+ __description = """oboom.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stanley", "stanley.foerster@gmail.com")]
+
+
+ RECAPTCHA_KEY = "6LdqpO0SAAAAAJGHXo63HyalP7H4qlRs_vff0kJX"
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.multiDL = self.resumeDownload = 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})
+
+
+ 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 _i in xrange(5):
+ response, challenge = 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], 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/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]
+ elif result[0] == 421:
+ self.retry(wait_time=result[2] + 60, reason=_("Connection limit exceeded"))
+ else:
+ self.fail(_("Could not retrieve download ticket. Error code: %s") % result[0])
diff --git a/pyload/plugin/hoster/OneFichierCom.py b/pyload/plugin/hoster/OneFichierCom.py
new file mode 100644
index 000000000..1c5c87a99
--- /dev/null
+++ b/pyload/plugin/hoster/OneFichierCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class OneFichierCom(SimpleHoster):
+ __name = "OneFichierCom"
+ __type = "hoster"
+ __version = "0.84"
+
+ __pattern = r'https?://(?:www\.)?(?:(?P<ID1>\w+)\.)?(?P<HOST>1fichier\.com|alterupload\.com|cjoint\.net|d(es)?fichiers\.com|dl4free\.com|megadl\.fr|mesfichiers\.org|piecejointe\.net|pjointe\.com|tenvoi\.com)(?:/\?(?P<ID2>\w+))?'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """1fichier.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("the-razer", "daniel_ AT gmx DOT net"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("imclem", ""),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Elrick69", "elrick69[AT]rocketmail[DOT]com"),
+ ("Walter Purcaro", "vuolter@gmail.com"),
+ ("Ludovic Lehmann", "ludo.lehmann@gmail.com")]
+
+
+ COOKIES = [("1fichier.com", "LG", "en")]
+ DISPOSITION = False #: Remove in 0.4.10
+
+ NAME_PATTERN = r'>FileName :</td>\s*<td.*>(?P<N>.+?)<'
+ SIZE_PATTERN = r'>Size :</td>\s*<td.*>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'File not found !\s*<'
+
+ WAIT_PATTERN = r'>You must wait \d+ minutes'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+
+ def handle_free(self, pyfile):
+ id = self.info['pattern']['ID1'] or self.info['pattern']['ID2']
+ url, inputs = self.parseHtmlForm('action="https://1fichier.com/\?%s' % id)
+
+ if not url:
+ self.fail(_("Download link not found"))
+
+ if "pass" in inputs:
+ inputs['pass'] = self.getPassword()
+
+ inputs['submit'] = "Download"
+
+ self.download(url, post=inputs)
+
+
+ def handle_premium(self, pyfile):
+ self.download(pyfile.url, post={'dl': "Download", 'did': 0})
diff --git a/pyload/plugin/hoster/OpenloadIo.py b/pyload/plugin/hoster/OpenloadIo.py
new file mode 100644
index 000000000..a13015473
--- /dev/null
+++ b/pyload/plugin/hoster/OpenloadIo.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class OpenloadIo(SimpleHoster):
+ __name = "OpenloadIo"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'https?://(?:www\.)?openload\.io/f/\w{11}'
+
+ __description = """Openload.io hoster plugin"""
+ __license = "GPLv3"
+ __authors = [(None, None)]
+
+
+ NAME_PATTERN = r'<span id="filename">(?P<N>.+)</'
+ SIZE_PATTERN = r'<span class="count">(?P<S>[\d.,]+) (?P<U>[\w^_]+)<'
+ OFFLINE_PATTERN = r">(We can't find the file you are looking for)"
+
+ LINK_FREE_PATTERN = r'id="realdownload"><a href="(https?://[\w\.]+\.openload\.io/dl/.*?)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
diff --git a/pyload/plugin/hoster/OronCom.py b/pyload/plugin/hoster/OronCom.py
new file mode 100644
index 000000000..29ca267a0
--- /dev/null
+++ b/pyload/plugin/hoster/OronCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class OronCom(DeadHoster):
+ __name = "OronCom"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'https?://(?:www\.)?oron\.com/\w{12}'
+ __config = []
+
+ __description = """Oron.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("chrox", "chrox@pyload.org"),
+ ("DHMH", "DHMH@pyload.org")]
diff --git a/pyload/plugin/hoster/OverLoadMe.py b/pyload/plugin/hoster/OverLoadMe.py
new file mode 100644
index 000000000..0ed1e1d57
--- /dev/null
+++ b/pyload/plugin/hoster/OverLoadMe.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import parseFileSize
+
+
+class OverLoadMe(MultiHoster):
+ __name = "OverLoadMe"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'https?://.*overload\.me/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Over-Load.me multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("marley", "marley@over-load.me")]
+
+
+ def setup(self):
+ self.chunkLimit = 5
+
+
+ def handle_premium(self, pyfile):
+ https = "https" if self.getConfig('ssl') else "http"
+ 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(data)
+
+ if data['error'] == 1:
+ self.logWarning(data['msg'])
+ self.tempOffline()
+ else:
+ if pyfile.name and pyfile.name.endswith('.tmp') and data['filename']:
+ pyfile.name = data['filename']
+ pyfile.size = parseFileSize(data['filesize'])
+
+ http_repl = ["http://", "https://"]
+ self.link = data['downloadlink'].replace(*http_repl if self.getConfig('ssl') else http_repl[::-1])
diff --git a/pyload/plugin/hoster/PandaplaNet.py b/pyload/plugin/hoster/PandaplaNet.py
new file mode 100644
index 000000000..dc23ef6a0
--- /dev/null
+++ b/pyload/plugin/hoster/PandaplaNet.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class PandaplaNet(DeadHoster):
+ __name = "PandaplaNet"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?pandapla\.net/\w{12}'
+ __config = []
+
+ __description = """Pandapla.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
diff --git a/pyload/plugin/hoster/PornhostCom.py b/pyload/plugin/hoster/PornhostCom.py
new file mode 100644
index 000000000..103882166
--- /dev/null
+++ b/pyload/plugin/hoster/PornhostCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+
+
+class PornhostCom(Hoster):
+ __name = "PornhostCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?pornhost\.com/(\d+/\d+\.html|\d+)'
+
+ __description = """Pornhost.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "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\d+\.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\d+\.pornhost\.com/\d+/.*?"',
+ 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\d+\.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|You will be redirected to', self.html):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/PornhubCom.py b/pyload/plugin/hoster/PornhubCom.py
new file mode 100644
index 000000000..c7bfa339e
--- /dev/null
+++ b/pyload/plugin/hoster/PornhubCom.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+
+
+class PornhubCom(Hoster):
+ __name = "PornhubCom"
+ __type = "hoster"
+ __version = "0.50"
+
+ __pattern = r'http://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=\w+'
+
+ __description = """Pornhub.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "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.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):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/PotloadCom.py b/pyload/plugin/hoster/PotloadCom.py
new file mode 100644
index 000000000..b6b86b689
--- /dev/null
+++ b/pyload/plugin/hoster/PotloadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class PotloadCom(DeadHoster):
+ __name = "PotloadCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?potload\.com/\w{12}'
+ __config = []
+
+ __description = """Potload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/PremiumTo.py b/pyload/plugin/hoster/PremiumTo.py
new file mode 100644
index 000000000..47169a6a3
--- /dev/null
+++ b/pyload/plugin/hoster/PremiumTo.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import fs_encode
+
+
+class PremiumTo(MultiHoster):
+ __name = "PremiumTo"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Premium.to multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("stickell", "l.stickell@yahoo.it")]
+
+
+ CHECK_TRAFFIC = True
+
+
+ def handle_premium(self, pyfile):
+ # raise timeout to 2min
+ self.download("http://premium.to/api/getfile.php",
+ get={'username': self.account.username,
+ 'password': self.account.password,
+ 'link' : pyfile.url},
+ disposition=True)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'nopremium': "No premium account available"}):
+ self.retry(60, 5 * 60, "No premium account available")
+
+ err = ''
+ if self.req.http.code == '420':
+ # Custom error code send - fail
+ file = fs_encode(self.lastDownload)
+ with open(file, "rb") as f:
+ err = f.read(256).strip()
+ os.remove(file)
+
+ if err:
+ self.fail(err)
+
+ return super(PremiumTo, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/PremiumizeMe.py b/pyload/plugin/hoster/PremiumizeMe.py
new file mode 100644
index 000000000..e23ba6b44
--- /dev/null
+++ b/pyload/plugin/hoster/PremiumizeMe.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class PremiumizeMe(MultiHoster):
+ __name = "PremiumizeMe"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.activate
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Premiumize.me multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")]
+
+
+ def handle_premium(self, pyfile):
+ # 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)
+ data = json_loads(self.load("https://api.premiumize.me/pm-api/v1.php",
+ get={'method' : "directdownloadlink",
+ 'params[login]': user,
+ 'params[pass]' : data['password'],
+ 'params[link]' : pyfile.url}))
+
+ # Check status and decide what to do
+ status = data['status']
+
+ if status == 200:
+ self.link = data['result']['location']
+ return
+
+ 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/plugin/hoster/PromptfileCom.py b/pyload/plugin/hoster/PromptfileCom.py
new file mode 100644
index 000000000..e3ec899c7
--- /dev/null
+++ b/pyload/plugin/hoster/PromptfileCom.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class PromptfileCom(SimpleHoster):
+ __name = "PromptfileCom"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'https?://(?:www\.)?promptfile\.com/'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Promptfile.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ 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_FREE_PATTERN = r'<a href=\"(.+)\" target=\"_blank\" class=\"view_dl_link\">Download File</a>'
+
+
+ def handle_free(self, pyfile):
+ # STAGE 1: get link to continue
+ m = re.search(self.CHASH_PATTERN, self.html)
+ if m is None:
+ self.error(_("CHASH_PATTERN not found"))
+ chash = m.group(1)
+ self.logDebug("Read chash %s" % chash)
+ # continue to stage2
+ self.html = self.load(pyfile.url, decode=True, post={'chash': chash})
+
+ # STAGE 2: get the direct link
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/PrzeklejPl.py b/pyload/plugin/hoster/PrzeklejPl.py
new file mode 100644
index 000000000..f4781abb9
--- /dev/null
+++ b/pyload/plugin/hoster/PrzeklejPl.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class PrzeklejPl(DeadHoster):
+ __name = "PrzeklejPl"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'http://(?:www\.)?przeklej\.pl/plik/.+'
+ __config = []
+
+ __description = """Przeklej.pl hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/PutdriveCom.py b/pyload/plugin/hoster/PutdriveCom.py
new file mode 100644
index 000000000..051da9473
--- /dev/null
+++ b/pyload/plugin/hoster/PutdriveCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.ZeveraCom import ZeveraCom
+
+
+class PutdriveCom(ZeveraCom):
+ __name = "PutdriveCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'https?://(?:www\.)putdrive\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
+
+ __description = """Multihosters.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/QuickshareCz.py b/pyload/plugin/hoster/QuickshareCz.py
new file mode 100644
index 000000000..f0afd1a95
--- /dev/null
+++ b/pyload/plugin/hoster/QuickshareCz.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class QuickshareCz(SimpleHoster):
+ __name = "QuickshareCz"
+ __type = "hoster"
+ __version = "0.56"
+
+ __pattern = r'http://(?:[^/]*\.)?quickshare\.cz/stahnout-soubor/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Quickshare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<th width="145px">Název:</th>\s*<td style="word-wrap:break-word;">(?P<N>[^<]+)</td>'
+ SIZE_PATTERN = r'<th>Velikost:</th>\s*<td>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</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+) = ([\d.]+|'.+?')", 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.handle_premium(pyfile)
+ else:
+ self.handle_free(pyfile)
+
+ if self.checkDownload({"error": re.compile(r"\AChyba!")}, max_size=100):
+ self.fail(_("File not m or plugin defect"))
+
+
+ def handle_free(self, pyfile):
+ # 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.load(download_url, post=data, follow_location=False)
+ self.header = self.req.http.header
+
+ m = re.search(r'Location\s*:\s*(.+)', self.header, re.I)
+ if m is None:
+ self.fail(_("File not found"))
+
+ self.link = m.group(1)
+ self.logDebug("FREE URL2:" + self.link)
+
+ # check errors
+ m = re.search(r'/chyba/(\d+)', self.link)
+ 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))
+
+
+ def handle_premium(self, pyfile):
+ 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.download(download_url, get=data)
diff --git a/pyload/plugin/hoster/RPNetBiz.py b/pyload/plugin/hoster/RPNetBiz.py
new file mode 100644
index 000000000..1d15aa3c7
--- /dev/null
+++ b/pyload/plugin/hoster/RPNetBiz.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import json_loads
+
+
+class RPNetBiz(MultiHoster):
+ __name = "RPNetBiz"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'https?://.+rpnet\.biz'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """RPNet.biz multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Dman", "dmanugm@gmail.com")]
+
+
+ def setup(self):
+ self.chunkLimit = -1
+
+
+ def handle_premium(self, pyfile):
+ user, data = self.account.selectAccount()
+
+ # Get the download link
+ res = 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" % res)
+ link_status = json_loads(res)['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))
+ res = 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" % res)
+ download_status = json_loads(res)['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.link = link_status['generated']
+ return
+ 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/plugin/hoster/RapideoPl.py b/pyload/plugin/hoster/RapideoPl.py
new file mode 100644
index 000000000..8df0a9a4d
--- /dev/null
+++ b/pyload/plugin/hoster/RapideoPl.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class RapideoPl(MultiHoster):
+ __name = "RapideoPl"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rapideo.pl multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("goddie", "dev@rapideo.pl")]
+
+
+ API_URL = "http://enc.rapideo.pl"
+
+ API_QUERY = {'site' : "newrd",
+ 'output' : "json",
+ 'username': "",
+ 'password': "",
+ 'url' : ""}
+
+ ERROR_CODES = {0 : "[%s] Incorrect login credentials",
+ 1 : "[%s] Not enough transfer to download - top-up your account",
+ 2 : "[%s] Incorrect / dead link",
+ 3 : "[%s] Error connecting to hosting, try again later",
+ 9 : "[%s] Premium account has expired",
+ 15: "[%s] Hosting no longer supported",
+ 80: "[%s] Too many incorrect login attempts, account blocked for 24h"}
+
+
+ def prepare(self):
+ super(RapideoPl, self).prepare()
+
+ data = self.account.getAccountData(self.user)
+
+ self.usr = data['usr']
+ self.pwd = data['pwd']
+
+
+ def runFileQuery(self, url, mode=None):
+ query = self.API_QUERY.copy()
+
+ query['username'] = self.usr
+ query['password'] = self.pwd
+ query['url'] = url
+
+ if mode == "fileinfo":
+ query['check'] = 2
+ query['loc'] = 1
+
+ self.logDebug(query)
+
+ return self.load(self.API_URL, post=query)
+
+
+ def handle_free(self, pyfile):
+ try:
+ data = self.runFileQuery(pyfile.url, 'fileinfo')
+
+ except Exception:
+ self.logDebug("RunFileQuery error")
+ self.tempOffline()
+
+ try:
+ parsed = json_loads(data)
+
+ except Exception:
+ self.logDebug("Loads error")
+ self.tempOffline()
+
+ self.logDebug(parsed)
+
+ if "errno" in parsed.keys():
+ if parsed['errno'] in self.ERROR_CODES:
+ # error code in known
+ self.fail(self.ERROR_CODES[parsed['errno']] % self.getClassName())
+ else:
+ # error code isn't yet added to plugin
+ self.fail(
+ parsed['errstring']
+ or _("Unknown error (code: %s)") % parsed['errno']
+ )
+
+ if "sdownload" in parsed:
+ if parsed['sdownload'] == "1":
+ self.fail(
+ _("Download from %s is possible only using Rapideo.pl website \
+ directly") % parsed['hosting'])
+
+ pyfile.name = parsed['filename']
+ pyfile.size = parsed['filesize']
+
+ try:
+ self.link = self.runFileQuery(pyfile.url, 'filedownload')
+
+ except Exception:
+ self.logDebug("runFileQuery error #2")
+ self.tempOffline()
diff --git a/pyload/plugin/hoster/RapidfileshareNet.py b/pyload/plugin/hoster/RapidfileshareNet.py
new file mode 100644
index 000000000..8252b49e7
--- /dev/null
+++ b/pyload/plugin/hoster/RapidfileshareNet.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class RapidfileshareNet(XFSHoster):
+ __name = "RapidfileshareNet"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?rapidfileshare\.net/\w{12}'
+
+ __description = """Rapidfileshare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("guidobelix", "guidobelix@hotmail.it")]
+
+
+ NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>.+?)">'
+ SIZE_PATTERN = r'>http://www.rapidfileshare.net/\w+?</font> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</font>'
+
+ OFFLINE_PATTERN = r'>No such file with this filename'
+ TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<'
diff --git a/pyload/plugin/hoster/RapidgatorNet.py b/pyload/plugin/hoster/RapidgatorNet.py
new file mode 100644
index 000000000..f989eadda
--- /dev/null
+++ b/pyload/plugin/hoster/RapidgatorNet.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugin.captcha.AdsCaptcha import AdsCaptcha
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+
+
+class RapidgatorNet(SimpleHoster):
+ __name = "RapidgatorNet"
+ __type = "hoster"
+ __version = "0.33"
+
+ __pattern = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rapidgator.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("chrox", ""),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ API_URL = "http://rapidgator.net/api/file"
+
+ COOKIES = [("rapidgator.net", "lang", "en")]
+
+ NAME_PATTERN = r'<title>Download file (?P<N>.*)</title>'
+ 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_PATTERN = r'You can download files up to|This file can be downloaded by premium only<'
+ 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).+'
+
+ LINK_FREE_PATTERN = r'return \'(http://\w+.rapidgator.net/.*)\';'
+
+ RECAPTCHA_PATTERN = r'"http://api\.recaptcha\.net/challenge\?k=(.*?)"'
+ ADSCAPTCHA_PATTERN = r'(http://api\.adscaptcha\.com/Get\.aspx[^"\']+)'
+ SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.script\?k=(.*?)"'
+
+
+ def setup(self):
+ if self.account:
+ self.sid = self.account.getAccountInfo(self.user).get('sid', None)
+ else:
+ self.sid = None
+
+ if self.sid:
+ self.premium = True
+
+ self.resumeDownload = self.multiDL = self.premium
+ self.chunkLimit = 1
+
+
+ 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 handle_premium(self, pyfile):
+ self.api_data = self.api_response('info')
+ self.api_data['md5'] = self.api_data['hash']
+
+ pyfile.name = self.api_data['filename']
+ pyfile.size = self.api_data['size']
+
+ self.link = self.api_response('download')['url']
+
+
+ def handle_free(self, pyfile):
+ jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html))
+ self.logDebug(jsvars)
+
+ self.req.http.lastURL = pyfile.url
+ self.req.http.c.setopt(pycurl.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(jsvars.get('secs', 45), False)
+
+ url = "http://rapidgator.net%s?sid=%s" % (
+ jsvars.get('getDownloadUrl', '/download/AjaxGetDownload'), jsvars['sid'])
+ jsvars.update(self.getJsonResponse(url))
+
+ self.req.http.lastURL = pyfile.url
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
+
+ url = "http://rapidgator.net%s" % jsvars.get('captchaUrl', '/download/captcha')
+ self.html = self.load(url)
+
+ for _i in xrange(5):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+ break
+ else:
+ captcha = self.handleCaptcha()
+
+ if not captcha:
+ self.error(_("Captcha pattern not found"))
+
+ response, challenge = captcha.challenge()
+
+ self.html = self.load(url, post={'DownloadCaptchaForm[captcha]': "",
+ 'adcopy_challenge' : challenge,
+ 'adcopy_response' : response})
+
+ if "The verification code is incorrect" in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ else:
+ self.error(_("Download link"))
+
+
+ def handleCaptcha(self):
+ for klass in (AdsCaptcha, ReCaptcha, SolveMedia):
+ inst = klass(self)
+ if inst.detect_key():
+ return inst
+
+
+ def getJsonResponse(self, url):
+ res = self.load(url, decode=True)
+ if not res.startswith('{'):
+ self.retry()
+ self.logDebug(url, res)
+ return json_loads(res)
diff --git a/pyload/plugin/hoster/RapiduNet.py b/pyload/plugin/hoster/RapiduNet.py
new file mode 100644
index 000000000..35f51e6af
--- /dev/null
+++ b/pyload/plugin/hoster/RapiduNet.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import time
+
+from pyload.utils import json_loads
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class RapiduNet(SimpleHoster):
+ __name = "RapiduNet"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'https?://(?:www\.)?rapidu\.net/(?P<ID>\d{10})'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rapidu.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("prOq", "")]
+
+
+ COOKIES = [("rapidu.net", "rapidu_lang", "en")]
+
+ INFO_PATTERN = r'<h1 title="(?P<N>.*)">.*</h1>\s*<small>(?P<S>\d+(\.\d+)?)\s(?P<U>\w+)</small>'
+ OFFLINE_PATTERN = r'<h1>404'
+
+ ERROR_PATTERN = r'<div class="error">'
+
+ RECAPTCHA_KEY = r'6Ld12ewSAAAAAHoE6WVP_pSfCdJcBQScVweQh8Io'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
+
+
+ def handle_free(self, pyfile):
+ self.req.http.lastURL = pyfile.url
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+
+ jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
+ get={'a': "getLoadTimeToDownload"},
+ post={'_go': ""},
+ decode=True)
+
+ if str(jsvars['timeToDownload']) is "stop":
+ t = (24 * 60 * 60) - (int(time.time()) % (24 * 60 * 60)) + time.altzone
+
+ self.logInfo("You've reach your daily download transfer")
+
+ self.retry(10, 10 if t < 1 else None, _("Try tomorrow again")) #@NOTE: check t in case of not synchronised clock
+
+ else:
+ self.wait(int(jsvars['timeToDownload']) - int(time.time()))
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ jsvars = self.getJsonResponse("https://rapidu.net/ajax.php",
+ get={'a': "getCheckCaptcha"},
+ post={'_go' : "",
+ 'captcha1': challenge,
+ 'captcha2': response,
+ 'fileId' : self.info['pattern']['ID']},
+ decode=True)
+
+ if jsvars['message'] == 'success':
+ self.link = jsvars['url']
+
+
+ def getJsonResponse(self, *args, **kwargs):
+ res = self.load(*args, **kwargs)
+ if not res.startswith('{'):
+ self.retry()
+
+ self.logDebug(res)
+
+ return json_loads(res)
diff --git a/pyload/plugin/hoster/RarefileNet.py b/pyload/plugin/hoster/RarefileNet.py
new file mode 100644
index 000000000..2c853c6ba
--- /dev/null
+++ b/pyload/plugin/hoster/RarefileNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class RarefileNet(XFSHoster):
+ __name = "RarefileNet"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'http://(?:www\.)?rarefile\.net/\w{12}'
+
+ __description = """Rarefile.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ LINK_PATTERN = r'<a href="(.+?)">\1</a>'
diff --git a/pyload/plugin/hoster/RealdebridCom.py b/pyload/plugin/hoster/RealdebridCom.py
new file mode 100644
index 000000000..ece063ccf
--- /dev/null
+++ b/pyload/plugin/hoster/RealdebridCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.utils import parseFileSize
+
+
+class RealdebridCom(MultiHoster):
+ __name = "RealdebridCom"
+ __type = "hoster"
+ __version = "0.67"
+
+ __pattern = r'https?://((?:www\.|s\d+\.)?real-debrid\.com/dl/|[\w^_]\.rdb\.so/d/)[\w^_]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Real-Debrid.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")]
+
+
+ def setup(self):
+ self.chunkLimit = 3
+
+
+ def handle_premium(self, pyfile):
+ data = json_loads(self.load("https://real-debrid.com/ajax/unrestrict.php",
+ get={'lang' : "en",
+ 'link' : pyfile.url,
+ 'password': self.getPassword(),
+ 'time' : int(time.time() * 1000)}))
+
+ 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 and pyfile.name.endswith('.tmp') and data['file_name']:
+ pyfile.name = data['file_name']
+ pyfile.size = parseFileSize(data['file_size'])
+ self.link = data['generated_links'][0][-1]
+
+ if self.getConfig('ssl'):
+ self.link = self.link.replace("http://", "https://")
+ else:
+ self.link = self.link.replace("https://", "http://")
diff --git a/pyload/plugin/hoster/RedtubeCom.py b/pyload/plugin/hoster/RedtubeCom.py
new file mode 100644
index 000000000..4b9afbc2b
--- /dev/null
+++ b/pyload/plugin/hoster/RedtubeCom.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import html_unescape
+
+
+class RedtubeCom(Hoster):
+ __name = "RedtubeCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?redtube\.com/\d+'
+
+ __description = """Redtube.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "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):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/RehostTo.py b/pyload/plugin/hoster/RehostTo.py
new file mode 100644
index 000000000..d63ca54b7
--- /dev/null
+++ b/pyload/plugin/hoster/RehostTo.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class RehostTo(MultiHoster):
+ __name = "RehostTo"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'https?://.*rehost\.to\..+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Rehost.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org")]
+
+
+ def handle_premium(self, pyfile):
+ self.download("http://rehost.to/process_download.php",
+ get={'user': "cookie",
+ 'pass': self.account.getAccountInfo(self.user)['session'],
+ 'dl' : pyfile.url},
+ disposition=True)
diff --git a/pyload/plugin/hoster/RemixshareCom.py b/pyload/plugin/hoster/RemixshareCom.py
new file mode 100644
index 000000000..0b331c1c6
--- /dev/null
+++ b/pyload/plugin/hoster/RemixshareCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://remixshare.com/download/z8uli
+#
+# 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.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class RemixshareCom(SimpleHoster):
+ __name = "RemixshareCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://remixshare\.com/(download|dl)/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Remixshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de" ),
+ ("Walter Purcaro", "vuolter@gmail.com" ),
+ ("sraedler" , "simon.raedler@yahoo.de")]
+
+
+ INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'>&nbsp;\((?P<S>\d+)&nbsp;(?P<U>[\w^_]+)\)<'
+ HASHSUM_PATTERN = r'>(?P<T>MD5): (?P<H>\w+)'
+ OFFLINE_PATTERN = r'<h1>Ooops!'
+
+ LINK_PATTERN = r'var uri = "(.+?)"'
+ TOKEN_PATTERN = r'var acc = (\d+)'
+
+ WAIT_PATTERN = r'var XYZ = "(\d+)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ b = re.search(self.LINK_PATTERN, self.html)
+ if not b:
+ self.error(_("File url"))
+
+ c = re.search(self.TOKEN_PATTERN, self.html)
+ if not c:
+ self.error(_("File token"))
+
+ self.link = b.group(1) + "/zzz/" + c.group(1)
diff --git a/pyload/plugin/hoster/RgHostNet.py b/pyload/plugin/hoster/RgHostNet.py
new file mode 100644
index 000000000..04ecbda8f
--- /dev/null
+++ b/pyload/plugin/hoster/RgHostNet.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class RgHostNet(SimpleHoster):
+ __name = "RgHostNet"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'http://(?:www\.)?rghost\.(net|ru)/[\d-]+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """RgHost.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
+
+
+ INFO_PATTERN = r'data-share42-text="(?P<N>.+?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ HASHSUM_PATTERN = r'<dt>(?P<T>\w+)</dt>\s*<dd>(?P<H>\w+)'
+ OFFLINE_PATTERN = r'>(File is deleted|page not found)'
+
+ LINK_FREE_PATTERN = r'<a href="(.+?)" class="btn large'
diff --git a/pyload/plugin/hoster/SafesharingEu.py b/pyload/plugin/hoster/SafesharingEu.py
new file mode 100644
index 000000000..0676015a2
--- /dev/null
+++ b/pyload/plugin/hoster/SafesharingEu.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class SafesharingEu(XFSHoster):
+ __name = "SafesharingEu"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?safesharing\.eu/\w{12}'
+
+ __description = """Safesharing.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ ERROR_PATTERN = r'(?:<div class="alert alert-danger">)(.+?)(?:</div>)'
diff --git a/pyload/plugin/hoster/SecureUploadEu.py b/pyload/plugin/hoster/SecureUploadEu.py
new file mode 100644
index 000000000..8cd310c2c
--- /dev/null
+++ b/pyload/plugin/hoster/SecureUploadEu.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class SecureUploadEu(XFSHoster):
+ __name = "SecureUploadEu"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?secureupload\.eu/\w{12}'
+
+ __description = """SecureUpload.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("z00nx", "z00nx0@gmail.com")]
+
+
+ INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>'
diff --git a/pyload/plugin/hoster/SendspaceCom.py b/pyload/plugin/hoster/SendspaceCom.py
new file mode 100644
index 000000000..39951f55b
--- /dev/null
+++ b/pyload/plugin/hoster/SendspaceCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class SendspaceCom(SimpleHoster):
+ __name = "SendspaceCom"
+ __type = "hoster"
+ __version = "0.17"
+
+ __pattern = r'https?://(?:www\.)?sendspace\.com/file/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Sendspace.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<h2 class="bgray">\s*<(?:b|strong)>(?P<N>[^<]+)</'
+ SIZE_PATTERN = r'<div class="file_description reverse margin_center">\s*<b>File Size:</b>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)\s*</div>'
+ OFFLINE_PATTERN = r'<div class="msg error" style="cursor: default">Sorry, the file you requested is not available.</div>'
+
+ LINK_FREE_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 handle_free(self, pyfile):
+ params = {}
+ for _i in xrange(3):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ if 'captcha_hash' in params:
+ self.correctCaptcha()
+ self.link = 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(pyfile.url, post=params)
+ else:
+ self.fail(_("Download link not found"))
diff --git a/pyload/plugin/hoster/Share4WebCom.py b/pyload/plugin/hoster/Share4WebCom.py
new file mode 100644
index 000000000..72e3e4a02
--- /dev/null
+++ b/pyload/plugin/hoster/Share4WebCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.hoster.UnibytesCom import UnibytesCom
+
+
+class Share4WebCom(UnibytesCom):
+ __name = "Share4WebCom"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'https?://(?:www\.)?share4web\.com/get/\w+'
+
+ __description = """Share4web.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "share4web.com"
diff --git a/pyload/plugin/hoster/Share76Com.py b/pyload/plugin/hoster/Share76Com.py
new file mode 100644
index 000000000..fda983401
--- /dev/null
+++ b/pyload/plugin/hoster/Share76Com.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class Share76Com(DeadHoster):
+ __name = "Share76Com"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'http://(?:www\.)?share76\.com/\w{12}'
+ __config = []
+
+ __description = """Share76.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
diff --git a/pyload/plugin/hoster/ShareFilesCo.py b/pyload/plugin/hoster/ShareFilesCo.py
new file mode 100644
index 000000000..37e386034
--- /dev/null
+++ b/pyload/plugin/hoster/ShareFilesCo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class ShareFilesCo(DeadHoster):
+ __name = "ShareFilesCo"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?sharefiles\.co/\w{12}'
+ __config = []
+
+ __description = """Sharefiles.co hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/SharebeesCom.py b/pyload/plugin/hoster/SharebeesCom.py
new file mode 100644
index 000000000..5b5017fc2
--- /dev/null
+++ b/pyload/plugin/hoster/SharebeesCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SharebeesCom(DeadHoster):
+ __name = "SharebeesCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?sharebees\.com/\w{12}'
+ __config = []
+
+ __description = """ShareBees hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/ShareonlineBiz.py b/pyload/plugin/hoster/ShareonlineBiz.py
new file mode 100644
index 000000000..ad56b83f4
--- /dev/null
+++ b/pyload/plugin/hoster/ShareonlineBiz.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urllib
+import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class ShareonlineBiz(SimpleHoster):
+ __name = "ShareonlineBiz"
+ __type = "hoster"
+ __version = "0.49"
+
+ __pattern = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download\.php\?id=|dl/)(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Shareonline.biz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", "http://www.share-online.biz/dl/\g<ID>")]
+
+ CHECK_TRAFFIC = True
+
+ RECAPTCHA_KEY = "6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX"
+
+ ERROR_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>'
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = {'name': urlparse.urlparse(urllib.unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 3 if url else 1, 'url': url}
+
+ if url:
+ info['pattern'] = re.match(cls.__pattern, url).groupdict()
+
+ field = getURL("http://api.share-online.biz/linkcheck.php",
+ get={'md5': "1"},
+ post={'links': info['pattern']['ID']},
+ decode=True).split(";")
+
+ if field[1] == "OK":
+ info['fileid'] = field[0]
+ info['status'] = 2
+ info['name'] = field[2]
+ info['size'] = field[3] #: in bytes
+ info['md5'] = field[4].strip().lower().replace("\n\n", "") #: md5
+
+ elif field[1] in ("DELETED", "NOT FOUND"):
+ info['status'] = 1
+
+ return info
+
+
+ def setup(self):
+ self.resumeDownload = self.premium
+ self.multiDL = False
+
+
+ def handleCaptcha(self):
+ recaptcha = ReCaptcha(self)
+
+ for _i in xrange(5):
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ m = re.search(r'var wait=(\d+);', self.html)
+ self.setWait(int(m.group(1)) if m else 30)
+
+ res = self.load("%s/free/captcha/%d" % (self.pyfile.url, int(time.time() * 1000)),
+ post={'dl_free' : "1",
+ 'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+ if not res == '0':
+ self.correctCaptcha()
+ return res
+ else:
+ self.invalidCaptcha()
+ else:
+ self.invalidCaptcha()
+ self.fail(_("No valid captcha solution received"))
+
+
+ def handle_free(self, pyfile):
+ self.wait(3)
+
+ self.html = self.load("%s/free/" % pyfile.url,
+ post={'dl_free': "1", 'choice': "free"},
+ decode=True)
+
+ self.checkErrors()
+
+ res = self.handleCaptcha()
+ self.link = res.decode('base64')
+
+ if not self.link.startswith("http://"):
+ self.error(_("Wrong download url"))
+
+ self.wait()
+
+
+ def checkFile(self, rules={}):
+ 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"))
+
+ return super(ShareonlineBiz, self).checkFile(rules)
+
+
+ def handle_premium(self, pyfile): #: should be working better loading (account) api internally
+ html = self.load("http://api.share-online.biz/account.php",
+ get={'username': self.user,
+ 'password': self.account.getAccountData(self.user)['password'],
+ 'act' : "download",
+ 'lid' : self.info['fileid']})
+
+ self.api_data = dlinfo = {}
+
+ for line in html.splitlines():
+ key, value = line.split(": ")
+ dlinfo[key.lower()] = value
+
+ self.logDebug(dlinfo)
+
+ if not dlinfo['status'] == "online":
+ self.offline()
+ else:
+ pyfile.name = dlinfo['name']
+ pyfile.size = int(dlinfo['size'])
+
+ self.link = dlinfo['url']
+
+ if self.link == "server_under_maintenance":
+ self.tempOffline()
+ else:
+ self.multiDL = True
+
+
+ def checkErrors(self):
+ m = re.search(r"/failure/(.*?)/1", self.req.lastEffectiveURL)
+ if m is None:
+ self.info.pop('error', None)
+ return
+
+ errmsg = m.group(1).lower()
+
+ try:
+ self.logError(errmsg, re.search(self.ERROR_PATTERN, self.html).group(1))
+ except Exception:
+ self.logError("Unknown error occurred", errmsg)
+
+ if errmsg is "invalid":
+ self.fail(_("File not available"))
+
+ elif errmsg in ("freelimit", "size", "proxy"):
+ self.fail(_("Premium account needed"))
+
+ elif errmsg in ("expired", "server"):
+ self.retry(wait_time=600, reason=errmsg)
+
+ elif 'slot' in errmsg:
+ self.wantReconnect = True
+ self.retry(24, 3600, errmsg)
+
+ else:
+ self.wantReconnect = True
+ self.retry(wait_time=60, reason=errmsg)
diff --git a/pyload/plugin/hoster/ShareplaceCom.py b/pyload/plugin/hoster/ShareplaceCom.py
new file mode 100644
index 000000000..b1361e859
--- /dev/null
+++ b/pyload/plugin/hoster/ShareplaceCom.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.Hoster import Hoster
+
+
+class ShareplaceCom(Hoster):
+ __name = "ShareplaceCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?shareplace\.(com|org)/\?\w+'
+
+ __description = """Shareplace.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("ACCakut", "")]
+
+
+ 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.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 = urllib.unquote(
+ url.replace("http://http:/", "").replace("vvvvvvvvv", "").replace("lllllllll", "").replace(
+ "teletubbies", ""))
+ self.logDebug("URL: %s" % url)
+ return url
+ else:
+ self.error(_("Absolute filepath not found"))
+
+
+ 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):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/SharingmatrixCom.py b/pyload/plugin/hoster/SharingmatrixCom.py
new file mode 100644
index 000000000..c4938f9bc
--- /dev/null
+++ b/pyload/plugin/hoster/SharingmatrixCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SharingmatrixCom(DeadHoster):
+ __name = "SharingmatrixCom"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?sharingmatrix\.com/file/\w+'
+ __config = []
+
+ __description = """Sharingmatrix.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("paulking", "")]
diff --git a/pyload/plugin/hoster/ShragleCom.py b/pyload/plugin/hoster/ShragleCom.py
new file mode 100644
index 000000000..c435d01a7
--- /dev/null
+++ b/pyload/plugin/hoster/ShragleCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class ShragleCom(DeadHoster):
+ __name = "ShragleCom"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?(cloudnator|shragle)\.com/files/(?P<ID>.+?)/'
+ __config = []
+
+ __description = """Cloudnator.com (Shragle.com) hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/SimplyPremiumCom.py b/pyload/plugin/hoster/SimplyPremiumCom.py
new file mode 100644
index 000000000..f4f7a3006
--- /dev/null
+++ b/pyload/plugin/hoster/SimplyPremiumCom.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+from pyload.plugin.internal.SimpleHoster import secondsToMidnight
+
+
+class SimplyPremiumCom(MultiHoster):
+ __name = "SimplyPremiumCom"
+ __type = "hoster"
+ __version = "0.08"
+
+ __pattern = r'https?://.+simply-premium\.com'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Simply-Premium.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("EvolutionClip", "evolutionclip@live.de")]
+
+
+ def setup(self):
+ self.chunkLimit = 16
+
+
+ def checkErrors(self):
+ if '<valid>0</valid>' in self.html or (
+ "You are not allowed to download from this host" in self.html and self.premium):
+ self.account.relogin(self.user)
+ self.retry()
+
+ elif "NOTFOUND" in self.html:
+ self.offline()
+
+ elif "downloadlimit" in self.html:
+ self.logWarning(_("Reached maximum connctions"))
+ self.retry(5, 60, _("Reached maximum connctions"))
+
+ elif "trafficlimit" in self.html:
+ self.logWarning(_("Reached daily limit for this host"))
+ self.retry(wait_time=secondsToMidnight(gmt=2), reason="Daily limit for this host reached")
+
+ elif "hostererror" in self.html:
+ self.logWarning(_("Hoster temporarily unavailable, waiting 1 minute and retry"))
+ self.retry(5, 60, _("Hoster is temporarily unavailable"))
+
+
+ def handle_premium(self, pyfile):
+ for _i in xrange(5):
+ self.html = self.load("http://www.simply-premium.com/premium.php", get={'info': "", 'link': self.pyfile.url})
+
+ if self.html:
+ self.logDebug("JSON data: " + self.html)
+ break
+ else:
+ self.logInfo(_("Unable to get API data, waiting 1 minute and retry"))
+ self.retry(5, 60, _("Unable to get API data"))
+
+ self.checkErrors()
+
+ try:
+ self.pyfile.name = re.search(r'<name>([^<]+)</name>', self.html).group(1)
+
+ except AttributeError:
+ self.pyfile.name = ""
+
+ try:
+ self.pyfile.size = re.search(r'<size>(\d+)</size>', self.html).group(1)
+
+ except AttributeError:
+ self.pyfile.size = 0
+
+ try:
+ self.link = re.search(r'<download>([^<]+)</download>', self.html).group(1)
+
+ except AttributeError:
+ self.link = 'http://www.simply-premium.com/premium.php?link=' + self.pyfile.url
diff --git a/pyload/plugin/hoster/SimplydebridCom.py b/pyload/plugin/hoster/SimplydebridCom.py
new file mode 100644
index 000000000..a053e0b76
--- /dev/null
+++ b/pyload/plugin/hoster/SimplydebridCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHoster import MultiHoster, replace_patterns
+
+
+class SimplydebridCom(MultiHoster):
+ __name = "SimplydebridCom"
+ __type = "hoster"
+ __version = "0.17"
+
+ __pattern = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd\.php'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Simply-debrid.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
+
+
+ def handle_premium(self, pyfile):
+ # fix the links for simply-debrid.com!
+ self.link = replace_patterns(pyfile.url, [("clz.to", "cloudzer.net/file")
+ ("http://share-online", "http://www.share-online")
+ ("ul.to", "uploaded.net/file")
+ ("uploaded.com", "uploaded.net")
+ ("filerio.com", "filerio.in")
+ ("lumfile.com", "lumfile.se")])
+
+ if 'fileparadox' in self.link:
+ self.link = self.link.replace("http://", "https://")
+
+ self.html = self.load("http://simply-debrid.com/api.php", get={'dl': self.link})
+ if 'tiger Link' in self.html or 'Invalid Link' in self.html or ('API' in self.html and 'ERROR' in self.html):
+ self.error(_("Unable to unrestrict link"))
+
+ self.link = self.html
+
+ self.wait(5)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({"error": "No address associated with hostname"}):
+ self.retry(24, 3 * 60, _("Bad file downloaded"))
+
+ return super(SimplydebridCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/SizedriveCom.py b/pyload/plugin/hoster/SizedriveCom.py
new file mode 100644
index 000000000..ece548921
--- /dev/null
+++ b/pyload/plugin/hoster/SizedriveCom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class SizedriveCom(SimpleHoster):
+ __name = "SizedriveCom"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?sizedrive\.com/[rd]/(?P<ID>\w+)'
+
+ __description = """Sizedrive.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("GammaC0de", None)]
+
+
+ NAME_PATTERN = r'>Nome:</b> (?P<N>.+?)<'
+ SIZE_PATTERN = r'>Tamanho:</b>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'ARQUIVO DELATADO POR'
+
+
+ def setup(self):
+ self.resumeDownload = False
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ self.wait(5)
+ self.html = self.load("http://www.sizedrive.com/getdownload.php",
+ post={'id': self.info['pattern']['ID']})
+
+ m = re.search(r'<span id="boton_download" ><a href="(.+?)"', self.html)
+ if m:
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/SmoozedCom.py b/pyload/plugin/hoster/SmoozedCom.py
new file mode 100644
index 000000000..83995b4cd
--- /dev/null
+++ b/pyload/plugin/hoster/SmoozedCom.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class SmoozedCom(MultiHoster):
+ __name = "SmoozedCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.activate
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Smoozed.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("", "")]
+
+
+ def handle_free(self, pyfile):
+ # 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)
+
+ # Check the link
+ get_data = {'session_key': self.account.getAccountInfo(self.user)['session'],
+ 'url' : pyfile.url}
+
+ data = json_loads(self.load("http://www2.smoozed.com/api/check", get=get_data))
+
+ if data['state'] != "ok":
+ self.fail(data['message'])
+
+ if data['data'].get("state", "ok") != "ok":
+ if data['data'] == "Offline":
+ self.offline()
+ else:
+ self.fail(data['data']['message'])
+
+ pyfile.name = data['data']['name']
+ pyfile.size = int(data['data']['size'])
+
+ # Start the download
+ header = self.load("http://www2.smoozed.com/api/download", get=get_data, just_header=True)
+
+ if not "location" in header:
+ self.fail(_("Unable to initialize download"))
+ else:
+ self.link = header['location'][-1] if isinstance(header['location'], list) else header['location']
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'error': '{"state":"error"}',
+ 'retry': '{"state":"retry"}'}):
+ self.fail(_("Error response received"))
+
+ return super(SmoozedCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/SockshareCom.py b/pyload/plugin/hoster/SockshareCom.py
new file mode 100644
index 000000000..f6cff5858
--- /dev/null
+++ b/pyload/plugin/hoster/SockshareCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SockshareCom(DeadHoster):
+ __name = "SockshareCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?sockshare\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
+ __config = []
+
+ __description = """Sockshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("stickell", "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/SolidfilesCom.py b/pyload/plugin/hoster/SolidfilesCom.py
new file mode 100644
index 000000000..9998f26ad
--- /dev/null
+++ b/pyload/plugin/hoster/SolidfilesCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.solidfiles.com/d/609cdb4b1b
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class SolidfilesCom(SimpleHoster):
+ __name = "SolidfilesCom"
+ __type = "hoster"
+ __version = "0.02"
+
+ __pattern = r'http://(?:www\.)?solidfiles\.com\/d/\w+'
+
+ __description = """Solidfiles.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("sraedler", "simon.raedler@yahoo.de")]
+
+
+ NAME_PATTERN = r'<h1 title="(?P<N>.+?)"'
+ SIZE_PATTERN = r'<p class="meta">(?P<S>[\d.,]+) (?P<U>[\w_^]+)'
+ OFFLINE_PATTERN = r'<h1>404'
+
+ LINK_FREE_PATTERN = r'id="ddl-text" href="(.+?)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
diff --git a/pyload/plugin/hoster/SoundcloudCom.py b/pyload/plugin/hoster/SoundcloudCom.py
new file mode 100644
index 000000000..15d2b9f17
--- /dev/null
+++ b/pyload/plugin/hoster/SoundcloudCom.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import json_loads
+
+
+class SoundcloudCom(SimpleHoster):
+ __name = "SoundcloudCom"
+ __type = "hoster"
+ __version = "0.11"
+
+ __pattern = r'https?://(?:www\.)?soundcloud\.com/[\w-]+/[\w-]+'
+ __config = [("use_premium", "bool" , "Use premium account if available", True ),
+ ("quality" , "Lower;Higher", "Quality" , "Higher")]
+
+ __description = """SoundCloud.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'title" content="(?P<N>.+?)"'
+ OFFLINE_PATTERN = r'<title>"SoundCloud - Hear the world’s sounds"</title>'
+
+
+ def handle_free(self, pyfile):
+ try:
+ song_id = re.search(r'sounds:(\d+)"', self.html).group(1)
+
+ except Exception:
+ self.error(_("Could not find song id"))
+
+ try:
+ client_id = re.search(r'"clientID":"(.+?)"', self.html).group(1)
+
+ except Exception:
+ client_id = "b45b1aa10f1ac2941910a7f0d10f8e28"
+
+ # url to retrieve the actual song url
+ streams = json_loads(self.load("https://api.soundcloud.com/tracks/%s/streams" % song_id,
+ get={'client_id': client_id}))
+
+ regex = re.compile(r'[^\d]')
+ http_streams = sorted([(key, value) for key, value in streams.iteritems() if key.startswith('http_')],
+ key=lambda t: regex.sub(t[0], ''),
+ reverse=True)
+
+ self.logDebug("Streams found: %s" % (http_streams or "None"))
+
+ if http_streams:
+ stream_name, self.link = http_streams[0 if self.getConfig('quality') == "Higher" else -1]
+ pyfile.name += '.' + stream_name.split('_')[1].lower()
diff --git a/pyload/plugin/hoster/SpeedLoadOrg.py b/pyload/plugin/hoster/SpeedLoadOrg.py
new file mode 100644
index 000000000..383d60817
--- /dev/null
+++ b/pyload/plugin/hoster/SpeedLoadOrg.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SpeedLoadOrg(DeadHoster):
+ __name = "SpeedLoadOrg"
+ __type = "hoster"
+ __version = "1.02"
+
+ __pattern = r'http://(?:www\.)?speedload\.org/(?P<ID>\w+)'
+ __config = []
+
+ __description = """Speedload.org hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/SpeedfileCz.py b/pyload/plugin/hoster/SpeedfileCz.py
new file mode 100644
index 000000000..bb1b2c527
--- /dev/null
+++ b/pyload/plugin/hoster/SpeedfileCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class SpeedfileCz(DeadHoster):
+ __name = "SpeedFileCz"
+ __type = "hoster"
+ __version = "0.32"
+
+ __pattern = r'http://(?:www\.)?speedfile\.cz/.+'
+ __config = []
+
+ __description = """Speedfile.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/SpeedyshareCom.py b/pyload/plugin/hoster/SpeedyshareCom.py
new file mode 100644
index 000000000..5ad141bc1
--- /dev/null
+++ b/pyload/plugin/hoster/SpeedyshareCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class SpeedyshareCom(SimpleHoster):
+ __name = "SpeedyshareCom"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?(speedyshare\.com|speedy\.sh)/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Speedyshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'class=downloadfilename>(?P<N>.*)</span></td>'
+ SIZE_PATTERN = r'class=sizetagtext>(?P<S>.*) (?P<U>[kKmM]?[iI]?[bB]?)</div>'
+
+ OFFLINE_PATTERN = r'class=downloadfilenamenotfound>.*</span>'
+
+ LINK_FREE_PATTERN = r'<a href=\'(.*)\'><img src=/gf/slowdownload\.png alt=\'Slow Download\' border=0'
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/StorageTo.py b/pyload/plugin/hoster/StorageTo.py
new file mode 100644
index 000000000..8c0cc046e
--- /dev/null
+++ b/pyload/plugin/hoster/StorageTo.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class StorageTo(DeadHoster):
+ __name = "StorageTo"
+ __type = "hoster"
+ __version = "0.01"
+
+ __pattern = r'http://(?:www\.)?storage\.to/get/.+'
+ __config = []
+
+ __description = """Storage.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("mkaay", "mkaay@mkaay.de")]
diff --git a/pyload/plugin/hoster/StreamCz.py b/pyload/plugin/hoster/StreamCz.py
new file mode 100644
index 000000000..0500574ca
--- /dev/null
+++ b/pyload/plugin/hoster/StreamCz.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.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.20"
+
+ __pattern = r'https?://(?:www\.)?stream\.cz/[^/]+/\d+'
+
+ __description = """Stream.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ 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.resumeDownload = True
+ self.multiDL = 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.error(_("CDN_PATTERN not found"))
+ 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.NAME_PATTERN, self.html)
+ if m is None:
+ self.error(_("NAME_PATTERN not found"))
+ 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") % cdnkey[-2:], download_url)
+ self.download(download_url)
diff --git a/pyload/plugin/hoster/StreamcloudEu.py b/pyload/plugin/hoster/StreamcloudEu.py
new file mode 100644
index 000000000..da759ae3b
--- /dev/null
+++ b/pyload/plugin/hoster/StreamcloudEu.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class StreamcloudEu(XFSHoster):
+ __name = "StreamcloudEu"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?streamcloud\.eu/\w{12}'
+
+ __description = """Streamcloud.eu hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("seoester", "seoester@googlemail.com")]
+
+
+ WAIT_PATTERN = r'var count = (\d+)'
+
+ LINK_PATTERN = r'file: "(http://(stor|cdn)\d+\.streamcloud\.eu:?\d*/.*/video\.(mp4|flv))",'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
diff --git a/pyload/plugin/hoster/TurbobitNet.py b/pyload/plugin/hoster/TurbobitNet.py
new file mode 100644
index 000000000..8138e2e23
--- /dev/null
+++ b/pyload/plugin/hoster/TurbobitNet.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+
+import binascii
+import pycurl
+import random
+import re
+import time
+import urllib
+
+import Crypto
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, timestamp
+
+
+class TurbobitNet(SimpleHoster):
+ __name = "TurbobitNet"
+ __type = "hoster"
+ __version = "0.19"
+
+ __pattern = r'http://(?:www\.)?turbobit\.net/(?:download/free/)?(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Turbobit.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("prOq", "")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", "http://turbobit.net/\g<ID>.html")]
+
+ COOKIES = [("turbobit.net", "user_lang", "en")]
+
+ NAME_PATTERN = r'id="file-title">(?P<N>.+?)<'
+ SIZE_PATTERN = r'class="file-size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File (?:was )?not found'
+
+ LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'(/download/redirect/[^"\']+)'
+
+ LIMIT_WAIT_PATTERN = r'<div id=\'timeout\'>(\d+)<'
+ CAPTCHA_PATTERN = r'<img alt="Captcha" src="(.+?)"'
+
+
+ def handle_free(self, pyfile):
+ self.html = self.load("http://turbobit.net/download/free/%s" % self.info['pattern']['ID'],
+ decode=True)
+
+ rtUpdate = self.getRtUpdate()
+
+ self.solveCaptcha()
+
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+
+ self.html = self.load(self.getDownloadUrl(rtUpdate))
+
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"])
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1)
+
+
+ def solveCaptcha(self):
+ for _i 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.error(_("Captcha form not found"))
+ self.logDebug(inputs)
+
+ if inputs['captcha_type'] == 'recaptcha':
+ recaptcha = ReCaptcha(self)
+ inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge()
+ else:
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("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 '<div class="captcha-error">Incorrect, try again!<' 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.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)
+ if m:
+ url = "http://turbobit.net%s%s" % m.groups()
+ else:
+ url = "http://turbobit.net/files/timeout.js?ver=%s" % "".join(random.choice('0123456789ABCDEF') for _i 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.info['pattern']['ID'], b, urllib.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")
+ else:
+ self.retry()
+
+ self.wait()
+
+
+ def decrypt(self, data):
+ cipher = Crypto.Cipher.ARC4.new(binascii.hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
+ return binascii.unhexlify(cipher.encrypt(binascii.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)
diff --git a/pyload/plugin/hoster/TurbouploadCom.py b/pyload/plugin/hoster/TurbouploadCom.py
new file mode 100644
index 000000000..1452f05be
--- /dev/null
+++ b/pyload/plugin/hoster/TurbouploadCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class TurbouploadCom(DeadHoster):
+ __name = "TurbouploadCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?turboupload\.com/(\w+)'
+ __config = []
+
+ __description = """Turboupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/TusfilesNet.py b/pyload/plugin/hoster/TusfilesNet.py
new file mode 100644
index 000000000..068b8df58
--- /dev/null
+++ b/pyload/plugin/hoster/TusfilesNet.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class TusfilesNet(XFSHoster):
+ __name = "TusfilesNet"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'https?://(?:www\.)?tusfiles\.net/\w{12}'
+
+ __description = """Tusfiles.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("guidobelix", "guidobelix@hotmail.it")]
+
+
+ INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)\['
+ OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!|The file you are trying to download is no longer available'
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.multiDL = True
+ self.resumeDownload = True
+
+
+ def downloadLink(self, link, disposition=True):
+ try:
+ return super(TusfilesNet, self).downloadLink(link, disposition)
+
+ except BadHeader, e:
+ if e.code is 503:
+ self.multiDL = False
+ raise Retry("503")
diff --git a/pyload/plugin/hoster/TwoSharedCom.py b/pyload/plugin/hoster/TwoSharedCom.py
new file mode 100644
index 000000000..988c50620
--- /dev/null
+++ b/pyload/plugin/hoster/TwoSharedCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class TwoSharedCom(SimpleHoster):
+ __name = "TwoSharedCom"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'http://(?:www\.)?2shared\.com/(account/)?(download|get|file|document|photo|video|audio)/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """2Shared.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<h1>(?P<N>.*)</h1>'
+ SIZE_PATTERN = r'<span class="dtitle">File size:</span>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted\.'
+
+ LINK_FREE_PATTERN = r'window.location =\'(.+?)\';'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
diff --git a/pyload/plugin/hoster/UlozTo.py b/pyload/plugin/hoster/UlozTo.py
new file mode 100644
index 000000000..83e2af953
--- /dev/null
+++ b/pyload/plugin/hoster/UlozTo.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+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 = "1.09"
+
+ __pattern = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(?:live/)?(?P<ID>\w+/[^/?]*)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Uloz.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ INFO_PATTERN = r'<p>File <strong>(?P<N>[^<]+)</strong> is password protected</p>'
+ NAME_PATTERN = r'<title>(?P<N>[^<]+) \| Uloz\.to</title>'
+ SIZE_PATTERN = r'<span id="fileSize">.*?(?P<S>[\d.,]+\s[kMG]?B)</span>'
+ OFFLINE_PATTERN = r'<title>404 - Page not found</title>|<h1 class="h1">File (has been deleted|was banned)</h1>'
+
+ URL_REPLACEMENTS = [(r'(?<=http://)([^/]+)', "www.ulozto.net")]
+ SIZE_REPLACEMENTS = [(r'([\d.]+)\s([kMG])B', convertDecimalPrefix)]
+
+ CHECK_TRAFFIC = True
+ DISPOSITION = False #: Remove in 0.4.10
+
+ ADULT_PATTERN = r'<form action="(.+?)" method="post" id="frm-askAgeForm">'
+ PASSWD_PATTERN = r'<div class="passwordProtectedFile">'
+ VIPLINK_PATTERN = r'<a href=".+?\?disclaimer=1" class="linkVip">'
+ TOKEN_PATTERN = r'<input type="hidden" name="_token_" .*?value="(.+?)"'
+
+
+ def setup(self):
+ self.chunkLimit = 16 if self.premium else 1
+ self.multiDL = True
+ self.resumeDownload = True
+
+
+ def handle_free(self, pyfile):
+ action, inputs = self.parseHtmlForm('id="frm-downloadDialog-freeDownloadForm"')
+ if not action or not inputs:
+ self.error(_("Free download form not found"))
+
+ 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.error(_("CAPTCHA form changed"))
+
+ self.download("http://www.ulozto.net" + action, post=inputs)
+
+
+ def handle_premium(self, pyfile):
+ self.download(pyfile.url, get={'do': "directDownload"})
+
+
+ def checkErrors(self):
+ if re.search(self.ADULT_PATTERN, self.html):
+ self.logInfo(_("Adult content confirmation needed"))
+
+ m = re.search(self.TOKEN_PATTERN, self.html)
+ if m is None:
+ self.error(_("TOKEN_PATTERN not found"))
+
+ self.html = self.load(pyfile.url,
+ get={'do': "askAgeForm-submit"},
+ post={"agree": "Confirm", "_token_": m.group(1)})
+
+ if self.PASSWD_PATTERN in self.html:
+ password = self.getPassword()
+
+ if password:
+ self.logInfo(_("Password protected link, trying ") + password)
+ self.html = self.load(pyfile.url,
+ get={'do': "passwordProtectedForm-submit"},
+ post={"password": password, "password_send": 'Send'})
+
+ if self.PASSWD_PATTERN in self.html:
+ self.fail(_("Incorrect password"))
+ else:
+ self.fail(_("No password found"))
+
+ if re.search(self.VIPLINK_PATTERN, self.html):
+ self.html = self.load(pyfile.url, get={'disclaimer': "1"})
+
+ return super(UlozTo, self).checkErrors()
+
+
+ def checkFile(self, rules={}):
+ 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.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"))
+
+ return super(UlozTo, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/UloziskoSk.py b/pyload/plugin/hoster/UloziskoSk.py
new file mode 100644
index 000000000..1fde11a78
--- /dev/null
+++ b/pyload/plugin/hoster/UloziskoSk.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UloziskoSk(SimpleHoster):
+ __name = "UloziskoSk"
+ __type = "hoster"
+ __version = "0.25"
+
+ __pattern = r'http://(?:www\.)?ulozisko\.sk/.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Ulozisko.sk hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<div class="down1">(?P<N>[^<]+)</div>'
+ SIZE_PATTERN = ur'Veğkosť súboru: <strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong><br />'
+ OFFLINE_PATTERN = ur'<span class = "red">ZadanÜ súbor neexistuje z jedného z nasledujúcich dÎvodov:</span>'
+
+ LINK_FREE_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:
+ self.link = "http://ulozisko.sk" + m.group(1)
+ else:
+ self.handle_free(pyfile)
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+ parsed_url = 'http://www.ulozisko.sk' + m.group(1)
+
+ m = re.search(self.ID_PATTERN, self.html)
+ if m is None:
+ self.error(_("ID_PATTERN not found"))
+ id = m.group(1)
+
+ self.logDebug("URL:" + parsed_url + ' ID:' + id)
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("CAPTCHA_PATTERN not found"))
+ 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" : pyfile.name,
+ "but" : "++++STIAHNI+S%DABOR++++"})
diff --git a/pyload/plugin/hoster/UnibytesCom.py b/pyload/plugin/hoster/UnibytesCom.py
new file mode 100644
index 000000000..52f376dcd
--- /dev/null
+++ b/pyload/plugin/hoster/UnibytesCom.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UnibytesCom(SimpleHoster):
+ __name = "UnibytesCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'https?://(?:www\.)?unibytes\.com/[\w .-]{11}B'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """UniBytes.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ HOSTER_DOMAIN = "unibytes.com"
+
+ INFO_PATTERN = r'<span[^>]*?id="fileName".*?>(?P<N>[^>]+)</span>\s*\((?P<S>\d.*?)\)'
+
+ WAIT_PATTERN = r'Wait for <span id="slowRest">(\d+)</span> sec'
+ LINK_FREE_PATTERN = r'<a href="(.+?)">Download</a>'
+
+
+ def handle_free(self, pyfile):
+ domain = "http://www.%s/" % self.HOSTER_DOMAIN
+ action, post_data = self.parseHtmlForm('id="startForm"')
+
+
+ for _i in xrange(8):
+ self.logDebug(action, post_data)
+ self.html = self.load(urlparse.urljoin(domain, action), post=post_data, follow_location=False)
+
+ m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I)
+ if m:
+ self.link = 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_FREE_PATTERN, self.html)
+ if m:
+ self.link = 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(m.group(1) if m else 60, False)
+
+ elif last_step in ("captcha", "last"):
+ post_data['captcha'] = self.decryptCaptcha(urlparse.urljoin(domain, "/captcha.jpg"))
+
+ else:
+ self.fail(_("No valid captcha code entered"))
diff --git a/pyload/plugin/hoster/UnrestrictLi.py b/pyload/plugin/hoster/UnrestrictLi.py
new file mode 100644
index 000000000..3735de6df
--- /dev/null
+++ b/pyload/plugin/hoster/UnrestrictLi.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UnrestrictLi(DeadHoster):
+ __name = "UnrestrictLi"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'https?://(?:www\.)?(unrestrict|unr)\.li/dl/[\w^_]+'
+
+ __description = """Unrestrict.li multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
diff --git a/pyload/plugin/hoster/UpleaCom.py b/pyload/plugin/hoster/UpleaCom.py
new file mode 100644
index 000000000..4e4db81a7
--- /dev/null
+++ b/pyload/plugin/hoster/UpleaCom.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class UpleaCom(XFSHoster):
+ __name = "UpleaCom"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'https?://(?:www\.)?uplea\.com/dl/\w{15}'
+
+ __description = """Uplea.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Redleon", None),
+ ("GammaC0de", None)]
+
+
+ DISPOSITION = False #@TODO: Remove in 0.4.10
+
+ HOSTER_DOMAIN = "uplea.com"
+
+ SIZE_REPLACEMENTS = [('ko','KB'), ('mo','MB'), ('go','GB'), ('Ko','KB'), ('Mo','MB'), ('Go','GB')]
+
+ NAME_PATTERN = r'<span class="gold-text">(?P<N>.+?)</span>'
+ SIZE_PATTERN = r'<span class="label label-info agmd">(?P<S>[\d.,]+) (?P<U>[\w^_]+?)</span>'
+ OFFLINE_PATTERN = r'>You followed an invalid or expired link'
+
+ LINK_PATTERN = r'"(https?://\w+\.uplea\.com/anonym/.*?)"'
+
+ PREMIUM_ONLY_PATTERN = r'You need to have a Premium subscription to download this file'
+ WAIT_PATTERN = r'timeText: ?([\d.]+),'
+ STEP_PATTERN = r'<a href="(/step/.+)">'
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+ self.resumeDownload = True
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.STEP_PATTERN, self.html)
+ if m is None:
+ self.error(_("STEP_PATTERN not found"))
+
+ self.html = self.load(urlparse.urljoin("http://uplea.com/", m.group(1)))
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.logDebug(_("Waiting %s seconds") % m.group(1))
+ self.wait(m.group(1), True)
+ self.retry()
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_PATTERN not found"))
+
+ self.link = m.group(1)
+ self.wait(15)
diff --git a/pyload/plugin/hoster/UploadStationCom.py b/pyload/plugin/hoster/UploadStationCom.py
new file mode 100644
index 000000000..ab55c71d4
--- /dev/null
+++ b/pyload/plugin/hoster/UploadStationCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadStationCom(DeadHoster):
+ __name = "UploadStationCom"
+ __type = "hoster"
+ __version = "0.52"
+
+ __pattern = r'http://(?:www\.)?uploadstation\.com/file/(?P<ID>\w+)'
+ __config = []
+
+ __description = """UploadStation.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UploadableCh.py b/pyload/plugin/hoster/UploadableCh.py
new file mode 100644
index 000000000..b5b8ee8a9
--- /dev/null
+++ b/pyload/plugin/hoster/UploadableCh.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UploadableCh(SimpleHoster):
+ __name = "UploadableCh"
+ __type = "hoster"
+ __version = "0.09"
+
+ __pattern = r'http://(?:www\.)?uploadable\.ch/file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Uploadable.ch hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.uploadable.ch/file/\g<ID>')]
+
+ INFO_PATTERN = r'div id=\"file_name\" title=.*>(?P<N>.+)<span class=\"filename_normal\">\((?P<S>[\d.]+) (?P<U>\w+)\)</span><'
+
+ OFFLINE_PATTERN = r'>(File not available|This file is no longer available)'
+ TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
+
+ WAIT_PATTERN = r'>Please wait.+?<'
+
+ RECAPTCHA_KEY = "6LdlJuwSAAAAAPJbPIoUhyqOJd7-yrah5Nhim5S3"
+
+
+ def handle_free(self, pyfile):
+ # Click the "free user" button and wait
+ a = self.load(pyfile.url, post={'downloadLink': "wait"}, decode=True)
+ self.logDebug(a)
+
+ self.wait(30)
+
+ # Make the recaptcha appear and show it the pyload interface
+ b = self.load(pyfile.url, post={'checkDownload': "check"}, decode=True)
+ self.logDebug(b) #: Expected output: {"success":"showCaptcha"}
+
+ recaptcha = ReCaptcha(self)
+
+ response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY)
+
+ # Submit the captcha solution
+ self.load("http://www.uploadable.ch/checkReCaptcha.php",
+ post={'recaptcha_challenge_field' : challenge,
+ 'recaptcha_response_field' : response,
+ 'recaptcha_shortencode_field': self.info['pattern']['ID']},
+ decode=True)
+
+ self.wait(3)
+
+ # Get ready for downloading
+ self.load(pyfile.url, post={'downloadLink': "show"}, decode=True)
+
+ self.wait(3)
+
+ # Download the file
+ self.download(pyfile.url, post={'download': "normal"}, disposition=True)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'wait': re.compile("Please wait for")}):
+ self.logInfo("Downloadlimit reached, please wait or reconnect")
+ self.wait(60 * 60, True)
+ self.retry()
+
+ return super(UploadableCh, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/UploadboxCom.py b/pyload/plugin/hoster/UploadboxCom.py
new file mode 100644
index 000000000..eb769919e
--- /dev/null
+++ b/pyload/plugin/hoster/UploadboxCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadboxCom(DeadHoster):
+ __name = "Uploadbox"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'http://(?:www\.)?uploadbox\.com/files/.+'
+ __config = []
+
+ __description = """UploadBox.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UploadedTo.py b/pyload/plugin/hoster/UploadedTo.py
new file mode 100644
index 000000000..f5877c1a9
--- /dev/null
+++ b/pyload/plugin/hoster/UploadedTo.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UploadedTo(SimpleHoster):
+ __name = "UploadedTo"
+ __type = "hoster"
+ __version = "0.87"
+
+ __pattern = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Uploaded.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ DISPOSITION = False
+
+ API_KEY = "lhF2IeeprweDfu9ccWlxXVVypA5nA3EL"
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://uploaded.net/file/\g<ID>')]
+
+ TEMP_OFFLINE_PATTERN = r'<title>uploaded\.net - Maintenance'
+
+ LINK_PREMIUM_PATTERN = r'<div class="tfree".*\s*<form method="post" action="(.+?)"'
+
+ WAIT_PATTERN = r'Current waiting period: <span>(\d+)'
+ DL_LIMIT_ERROR = r'You have reached the max. number of possible free downloads for this hour'
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ info = super(UploadedTo, cls).apiInfo(url)
+
+ for _i in xrange(5):
+ html = getURL("http://uploaded.net/api/filemultiple",
+ get={"apikey": cls.API_KEY, 'id_0': re.match(cls.__pattern, url).group('ID')},
+ decode=True)
+
+ if html != "can't find request":
+ api = html.split(",", 4)
+ if api[0] == "online":
+ info.update({'name': api[4].strip(), 'size': api[2], 'status': 2})
+ else:
+ info['status'] = 1
+ break
+ else:
+ time.sleep(3)
+
+ return info
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+ self.chunkLimit = 1 #: critical problems with more chunks
+
+
+ def checkErrors(self):
+ if 'var free_enabled = false;' in self.html:
+ self.logError(_("Free-download capacities exhausted"))
+ self.retry(24, 5 * 60)
+
+ elif "limit-size" in self.html:
+ self.fail(_("File too big for free download"))
+
+ elif "limit-slot" in self.html: #: Temporary restriction so just wait a bit
+ self.wait(30 * 60, True)
+ self.retry()
+
+ elif "limit-parallel" in self.html:
+ self.fail(_("Cannot download in parallel"))
+
+ elif "limit-dl" in self.html or self.DL_LIMIT_ERROR in self.html: #: limit-dl
+ self.wait(3 * 60 * 60, True)
+ self.retry()
+
+ elif '"err":"captcha"' in self.html:
+ self.invalidCaptcha()
+
+ else:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(m.group(1))
+
+
+ def handle_free(self, pyfile):
+ self.load("http://uploaded.net/language/en", just_header=True)
+
+ self.html = self.load("http://uploaded.net/js/download.js", decode=True)
+
+ recaptcha = ReCaptcha(self)
+ response, challenge = recaptcha.challenge()
+
+ self.html = self.load("http://uploaded.net/io/ticket/captcha/%s" % self.info['pattern']['ID'],
+ post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ if "type:'download'" in self.html:
+ self.correctCaptcha()
+ try:
+ self.link = re.search("url:'(.+?)'", self.html).group(1)
+
+ except Exception:
+ pass
+
+ self.checkErrors()
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({'limit-dl': self.DL_LIMIT_ERROR}):
+ self.wait(3 * 60 * 60, True)
+ self.retry()
+
+ return super(UploadedTo, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/UploadhereCom.py b/pyload/plugin/hoster/UploadhereCom.py
new file mode 100644
index 000000000..789f5e9f7
--- /dev/null
+++ b/pyload/plugin/hoster/UploadhereCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadhereCom(DeadHoster):
+ __name = "UploadhereCom"
+ __type = "hoster"
+ __version = "0.12"
+
+ __pattern = r'http://(?:www\.)?uploadhere\.com/\w{10}'
+ __config = []
+
+ __description = """Uploadhere.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UploadheroCom.py b/pyload/plugin/hoster/UploadheroCom.py
new file mode 100644
index 000000000..f112d9c56
--- /dev/null
+++ b/pyload/plugin/hoster/UploadheroCom.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://uploadhero.co/dl/wQBRAVSM
+
+import re
+import urlparse
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UploadheroCom(SimpleHoster):
+ __name = "UploadheroCom"
+ __type = "hoster"
+ __version = "0.18"
+
+ __pattern = r'http://(?:www\.)?uploadhero\.com?/dl/\w+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """UploadHero.co plugin"""
+ __license = "GPLv3"
+ __authors = [("mcmyst", "mcmyst@hotmail.fr"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'<div class="nom_de_fichier">(?P<N>.+?)<'
+ SIZE_PATTERN = r'>Filesize: </span><strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'<p class="titre_dl_2">'
+
+ 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\?\w+)"'
+
+ LINK_FREE_PATTERN = r'var magicomfg = \'<a href="(.+?)"|"(http://storage\d+\.uploadhero\.co.+?)"'
+ LINK_PREMIUM_PATTERN = r'<a href="(.+?)" id="downloadnow"'
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.error(_("Captcha not found"))
+
+ captcha = self.decryptCaptcha(urlparse.urljoin("http://uploadhero.co", m.group(1)))
+
+ self.html = self.load(pyfile.url,
+ get={"code": captcha})
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m:
+ self.link = m.group(1) or m.group(2)
+ self.wait(50)
+
+
+ def checkErrors(self):
+ m = re.search(self.IP_BLOCKED_PATTERN, self.html)
+ if m:
+ self.html = self.load(urlparse.urljoin("http://uploadhero.co", 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()
+
+ return super(UploadheroCom, self).checkErrors()
diff --git a/pyload/plugin/hoster/UploadingCom.py b/pyload/plugin/hoster/UploadingCom.py
new file mode 100644
index 000000000..6bf3988cb
--- /dev/null
+++ b/pyload/plugin/hoster/UploadingCom.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, timestamp
+
+
+class UploadingCom(SimpleHoster):
+ __name = "UploadingCom"
+ __type = "hoster"
+ __version = "0.40"
+
+ __pattern = r'http://(?:www\.)?uploading\.com/files/(?:get/)?(?P<ID>\w+)'
+
+ __description = """Uploading.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("mkaay", "mkaay@mkaay.de"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'id="file_title">(?P<N>.+)</'
+ SIZE_PATTERN = r'size tip_container">(?P<S>[\d.,]+) (?P<U>[\w^_]+)<'
+ OFFLINE_PATTERN = r'(Page|file) not found'
+
+ COOKIES = [("uploading.com", "lang", "1"),
+ (".uploading.com", "language", "1"),
+ (".uploading.com", "setlang", "en"),
+ (".uploading.com", "_lang", "en")]
+
+
+ def process(self, pyfile):
+ if not "/get/" in pyfile.url:
+ pyfile.url = pyfile.url.replace("/files", "/files/get")
+
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ if self.premium:
+ self.handle_premium(pyfile)
+ else:
+ self.handle_free(pyfile)
+
+
+ def handle_premium(self, pyfile):
+ postData = {'action': 'get_link',
+ 'code' : self.info['pattern']['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:
+ self.link = url.group(1).replace("\\/", "/")
+
+ raise Exception("Plugin defect")
+
+
+ def handle_free(self, pyfile):
+ m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html)
+ if m:
+ pyfile.error = m.group(1)
+ self.logWarning(pyfile.error)
+ self.retry(6, (6 * 60 if m.group(2) else 15) * 60, pyfile.error)
+
+ ajax_url = "http://uploading.com/files/get/?ajax"
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = pyfile.url
+
+ res = json_loads(self.load(ajax_url, post={'action': 'second_page', 'code': self.info['pattern']['ID']}))
+
+ if 'answer' in res and 'wait_time' in res['answer']:
+ wait_time = int(res['answer']['wait_time'])
+ self.logInfo(_("Waiting %d seconds") % wait_time)
+ self.wait(wait_time)
+ else:
+ self.error(_("No AJAX/WAIT"))
+
+ res = json_loads(self.load(ajax_url, post={'action': 'get_link', 'code': self.info['pattern']['ID'], 'pass': 'false'}))
+
+ if 'answer' in res and 'link' in res['answer']:
+ url = res['answer']['link']
+ else:
+ self.error(_("No 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.error(_("No URL"))
+
+ self.link = url
diff --git a/pyload/plugin/hoster/UploadkingCom.py b/pyload/plugin/hoster/UploadkingCom.py
new file mode 100644
index 000000000..31d8874d8
--- /dev/null
+++ b/pyload/plugin/hoster/UploadkingCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class UploadkingCom(DeadHoster):
+ __name = "UploadkingCom"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'http://(?:www\.)?uploadking\.com/\w{10}'
+ __config = []
+
+ __description = """UploadKing.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
diff --git a/pyload/plugin/hoster/UpstoreNet.py b/pyload/plugin/hoster/UpstoreNet.py
new file mode 100644
index 000000000..7d21d16ac
--- /dev/null
+++ b/pyload/plugin/hoster/UpstoreNet.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class UpstoreNet(SimpleHoster):
+ __name = "UpstoreNet"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://(?:www\.)?upstore\.net/'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Upstore.Net File Download Hoster"""
+ __license = "GPLv3"
+ __authors = [("igel", "igelkun@myopera.com")]
+
+
+ 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_FREE_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>'
+
+
+ def handle_free(self, pyfile):
+ # STAGE 1: get link to continue
+ m = re.search(self.CHASH_PATTERN, self.html)
+ if m is None:
+ self.error(_("CHASH_PATTERN not found"))
+ chash = m.group(1)
+ self.logDebug("Read hash " + chash)
+ # continue to stage2
+ post_data = {'hash': chash, 'free': 'Slow download'}
+ self.html = self.load(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)
+
+ # try the captcha 5 times
+ for _i in xrange(5):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m is None:
+ self.error(_("Wait pattern not found"))
+ wait_time = int(m.group(1))
+
+ # then, do the waiting
+ self.wait(wait_time)
+
+ # then, handle the captcha
+ response, challenge = recaptcha.challenge()
+ post_data.update({'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field' : response})
+
+ self.html = self.load(pyfile.url, post=post_data, decode=True)
+
+ # STAGE 3: get direct link
+ m = re.search(self.LINK_FREE_PATTERN, self.html, re.S)
+ if m:
+ break
+
+ if m is None:
+ self.error(_("Download link not found"))
+
+ self.link = m.group(1)
diff --git a/pyload/plugin/hoster/UptoboxCom.py b/pyload/plugin/hoster/UptoboxCom.py
new file mode 100644
index 000000000..a2d1e8fcc
--- /dev/null
+++ b/pyload/plugin/hoster/UptoboxCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class UptoboxCom(XFSHoster):
+ __name = "UptoboxCom"
+ __type = "hoster"
+ __version = "0.18"
+
+ __pattern = r'https?://(?:www\.)?(uptobox|uptostream)\.com/\w{12}'
+
+ __description = """Uptobox.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ 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'>Service Unavailable'
+
+ LINK_PATTERN = r'"(https?://\w+\.uptobox\.com/d/.*?)"'
+
+ ERROR_PATTERN = r'>(You have to wait.+till next download.)<' #@TODO: Check XFSHoster ERROR_PATTERN
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+ self.resumeDownload = True
diff --git a/pyload/plugin/hoster/VeehdCom.py b/pyload/plugin/hoster/VeehdCom.py
new file mode 100644
index 000000000..6e28164de
--- /dev/null
+++ b/pyload/plugin/hoster/VeehdCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.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"""
+ __license = "GPLv3"
+ __authors = [("cat", "cat@pyload")]
+
+
+ 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.logDebug("Requesting page: %s" % 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.error(_("Video title not found"))
+
+ name = m.group(1)
+
+ # replace unwanted characters in filename
+ if self.getConfig('filename_spaces'):
+ pattern = '[^\w ]+'
+ else:
+ pattern = '[^\w.]+'
+
+ 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.error(_("Embedded video url not found"))
+
+ return m.group(1)
diff --git a/pyload/plugin/hoster/VeohCom.py b/pyload/plugin/hoster/VeohCom.py
new file mode 100644
index 000000000..4461b547a
--- /dev/null
+++ b/pyload/plugin/hoster/VeohCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class VeohCom(SimpleHoster):
+ __name = "VeohCom"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)'
+ __config = [("use_premium", "bool" , "Use premium account if available", True ),
+ ("quality" , "Low;High;Auto", "Quality" , "Auto")]
+
+ __description = """Veoh.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ NAME_PATTERN = r'<meta name="title" content="(?P<N>.*?)"'
+ OFFLINE_PATTERN = r'>Sorry, we couldn\'t find the video you were looking for'
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.veoh.com/watch/\g<ID>')]
+
+ COOKIES = [("veoh.com", "lassieLocale", "en")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = -1
+
+
+ def handle_free(self, pyfile):
+ 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:
+ pyfile.name += ".mp4"
+ self.link = m.group(1).replace("\\", "")
+ return
+ else:
+ self.logInfo(_("No %s quality video found") % q.upper())
+ else:
+ self.fail(_("No video found!"))
diff --git a/pyload/plugin/hoster/VidPlayNet.py b/pyload/plugin/hoster/VidPlayNet.py
new file mode 100644
index 000000000..5d98d2fb3
--- /dev/null
+++ b/pyload/plugin/hoster/VidPlayNet.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://vidplay.net/38lkev0h3jv0
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class VidPlayNet(XFSHoster):
+ __name = "VidPlayNet"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?vidplay\.net/\w{12}'
+
+ __description = """VidPlay.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")]
+
+
+ NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>'
diff --git a/pyload/plugin/hoster/VimeoCom.py b/pyload/plugin/hoster/VimeoCom.py
new file mode 100644
index 000000000..86abf362e
--- /dev/null
+++ b/pyload/plugin/hoster/VimeoCom.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class VimeoCom(SimpleHoster):
+ __name = "VimeoCom"
+ __type = "hoster"
+ __version = "0.04"
+
+ __pattern = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)'
+ __config = [("use_premium", "bool" , "Use premium account if available" , True ),
+ ("quality" , "Lowest;Mobile;SD;HD;Highest", "Quality" , "Highest"),
+ ("original" , "bool" , "Try to download the original file", True )]
+
+ __description = """Vimeo.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ 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.<'
+
+ URL_REPLACEMENTS = [(__pattern + ".*", r'https://www.vimeo.com/\g<ID>')]
+
+ COOKIES = [("vimeo.com", "language", "en")]
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = True
+ self.chunkLimit = -1
+
+
+ def handle_free(self, pyfile):
+ password = self.getPassword()
+
+ if self.js and 'class="btn iconify_down_b"' in self.html:
+ html = self.js.eval(self.load(pyfile.url, get={'action': "download", 'password': password}, decode=True))
+ pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) '
+ else:
+ html = self.load("https://player.vimeo.com/video/" + self.info['pattern']['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.link = 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.link = link[q]
+ return
+ else:
+ self.logInfo(_("No %s quality video found") % q.upper())
+ else:
+ self.fail(_("No video found!"))
diff --git a/pyload/plugin/hoster/Vipleech4UCom.py b/pyload/plugin/hoster/Vipleech4UCom.py
new file mode 100644
index 000000000..cd68410f0
--- /dev/null
+++ b/pyload/plugin/hoster/Vipleech4UCom.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class Vipleech4UCom(DeadHoster):
+ __name = "Vipleech4UCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?vipleech4u\.com/manager\.php'
+ __config = []
+
+ __description = """Vipleech4u.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Kagenoshin", "kagenoshin@gmx.ch")]
diff --git a/pyload/plugin/hoster/WarserverCz.py b/pyload/plugin/hoster/WarserverCz.py
new file mode 100644
index 000000000..2556a1393
--- /dev/null
+++ b/pyload/plugin/hoster/WarserverCz.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class WarserverCz(DeadHoster):
+ __name = "WarserverCz"
+ __type = "hoster"
+ __version = "0.13"
+
+ __pattern = r'http://(?:www\.)?warserver\.cz/stahnout/\d+'
+ __config = []
+
+ __description = """Warserver.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
diff --git a/pyload/plugin/hoster/WebshareCz.py b/pyload/plugin/hoster/WebshareCz.py
new file mode 100644
index 000000000..5608cd272
--- /dev/null
+++ b/pyload/plugin/hoster/WebshareCz.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class WebshareCz(SimpleHoster):
+ __name = "WebshareCz"
+ __type = "hoster"
+ __version = "0.16"
+
+ __pattern = r'https?://(?:www\.)?webshare\.cz/(?:#/)?file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """WebShare.cz hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it"),
+ ("rush", "radek.senfeld@gmail.com")]
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = super(WebshareCz, cls).getInfo(url, html)
+
+ if url:
+ info['pattern'] = re.match(cls.__pattern, url).groupdict()
+
+ api_data = getURL("https://webshare.cz/api/file_info/",
+ post={'ident': info['pattern']['ID']},
+ decode=True)
+
+ if 'File not found' in api_data:
+ info['status'] = 1
+ else:
+ info['status'] = 2
+ info['name'] = re.search('<name>(.+)</name>', api_data).group(1) or info['name']
+ info['size'] = re.search('<size>(.+)</size>', api_data).group(1) or info['size']
+
+ return info
+
+
+ def handle_free(self, pyfile):
+ wst = self.account.infos['wst'] if self.account and 'wst' in self.account.infos else ""
+
+ api_data = getURL('https://webshare.cz/api/file_link/',
+ post={'ident': self.info['pattern']['ID'], 'wst': wst},
+ decode=True)
+
+ self.logDebug("API data: " + api_data)
+
+ m = re.search('<link>(.+)</link>', api_data)
+ if m is None:
+ self.error(_("Unable to detect direct link"))
+
+ self.link = m.group(1)
+
+
+ def handle_premium(self, pyfile):
+ return self.handle_free(pyfile)
diff --git a/pyload/plugin/hoster/WrzucTo.py b/pyload/plugin/hoster/WrzucTo.py
new file mode 100644
index 000000000..00af10b23
--- /dev/null
+++ b/pyload/plugin/hoster/WrzucTo.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class WrzucTo(SimpleHoster):
+ __name = "WrzucTo"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'http://(?:www\.)?wrzuc\.to/(\w+(\.wt|\.html)|(\w+/?linki/\w+))'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Wrzuc.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'id="file_info">\s*<strong>(?P<N>.*?)</strong>'
+ SIZE_PATTERN = r'class="info">\s*<tr>\s*<td>(?P<S>.*?)</td>'
+
+ COOKIES = [("wrzuc.to", "language", "en")]
+
+
+ def setup(self):
+ self.multiDL = True
+
+
+ def handle_free(self, pyfile):
+ data = dict(re.findall(r'(md5|file): "(.*?)"', self.html))
+ if len(data) != 2:
+ self.error(_("No file ID"))
+
+ self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = pyfile.url
+ self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']})
+
+ self.req.http.lastURL = 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.error(_("No download URL"))
+
+ self.link = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link'])
diff --git a/pyload/plugin/hoster/WuploadCom.py b/pyload/plugin/hoster/WuploadCom.py
new file mode 100644
index 000000000..2af68ced7
--- /dev/null
+++ b/pyload/plugin/hoster/WuploadCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class WuploadCom(DeadHoster):
+ __name = "WuploadCom"
+ __type = "hoster"
+ __version = "0.23"
+
+ __pattern = r'http://(?:www\.)?wupload\..+?/file/((\w+/)?\d+)(/.*)?'
+ __config = []
+
+ __description = """Wupload.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("Paul King", "")]
diff --git a/pyload/plugin/hoster/X7To.py b/pyload/plugin/hoster/X7To.py
new file mode 100644
index 000000000..865fc4b45
--- /dev/null
+++ b/pyload/plugin/hoster/X7To.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class X7To(DeadHoster):
+ __name = "X7To"
+ __type = "hoster"
+ __version = "0.41"
+
+ __pattern = r'http://(?:www\.)?x7\.to/'
+ __config = []
+
+ __description = """X7.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("ernieb", "ernieb")]
diff --git a/pyload/plugin/hoster/XFileSharingPro.py b/pyload/plugin/hoster/XFileSharingPro.py
new file mode 100644
index 000000000..140a69a72
--- /dev/null
+++ b/pyload/plugin/hoster/XFileSharingPro.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.internal.XFSHoster import XFSHoster
+
+
+class XFileSharingPro(XFSHoster):
+ __name = "XFileSharingPro"
+ __type = "hoster"
+ __version = "0.45"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """XFileSharingPro dummy hoster plugin for hook"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ URL_REPLACEMENTS = [("/embed-", "/")]
+
+
+ def _log(self, type, args):
+ msg = " | ".join(str(a).strip() for a in args if a)
+ logger = getattr(self.log, type)
+ logger("%s: %s: %s" % (self.getClassName(), self.HOSTER_NAME, msg or _("%s MARK" % type.upper())))
+
+
+ def init(self):
+ super(XFileSharingPro, self).init()
+
+ self.__pattern = self.core.pluginManager.hosterPlugins[self.getClassName()]['pattern']
+
+ self.HOSTER_DOMAIN = re.match(self.__pattern, self.pyfile.url).group("DOMAIN").lower()
+ self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.')
+
+ account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME)
+
+ if account and account.canUse():
+ self.account = account
+
+ elif self.account:
+ self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN
+
+ else:
+ return
+
+ self.user, data = self.account.selectAccount()
+ self.req = self.account.getAccountRequest(self.user)
+ self.premium = self.account.isPremium(self.user)
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.resumeDownload = self.premium
+ self.multiDL = True
diff --git a/pyload/plugin/hoster/XHamsterCom.py b/pyload/plugin/hoster/XHamsterCom.py
new file mode 100644
index 000000000..4be06833b
--- /dev/null
+++ b/pyload/plugin/hoster/XHamsterCom.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.utils import json_loads
+from pyload.plugin.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"""
+ __license = "GPLv3"
+ __authors = []
+
+
+ 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.S)
+ json_flashvar = flashvar_pattern.search(self.html)
+
+ if not json_flashvar:
+ self.error(_("flashvar not found"))
+
+ j = clean_json(json_flashvar.group(1))
+ flashvars = json_loads(j)
+
+ if flashvars['srv']:
+ srv_url = flashvars['srv'] + '/'
+ else:
+ self.error(_("srv_url not found"))
+
+ if flashvars['url_mode']:
+ url_mode = flashvars['url_mode']
+
+
+ else:
+ self.error(_("url_mode not found"))
+
+ if self.desired_fmt == ".mp4":
+ file_url = re.search(r"<a href=\"" + srv_url + "(.+?)\"", self.html)
+ if file_url is None:
+ self.error(_("file_url not found"))
+ file_url = file_url.group(1)
+ long_url = srv_url + file_url
+ self.logDebug("long_url = " + long_url)
+ else:
+ if flashvars['file']:
+ file_url = urllib.unquote(flashvars['file'])
+ else:
+ self.error(_("file_url not found"))
+
+ if url_mode == '3':
+ long_url = file_url
+ self.logDebug("long_url = " + long_url)
+ else:
+ long_url = srv_url + "key=" + file_url
+ self.logDebug("long_url = " + 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):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/XVideosCom.py b/pyload/plugin/hoster/XVideosCom.py
new file mode 100644
index 000000000..be168fbb9
--- /dev/null
+++ b/pyload/plugin/hoster/XVideosCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.Hoster import Hoster
+
+
+class XVideosCom(Hoster):
+ __name = "XVideos.com"
+ __type = "hoster"
+ __version = "0.10"
+
+ __pattern = r'http://(?:www\.)?xvideos\.com/video(\d+)'
+
+ __description = """XVideos.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+
+ 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(urllib.unquote(re.search(r"flv_url=([^&]+)&", site).group(1)))
diff --git a/pyload/plugin/hoster/XdadevelopersCom.py b/pyload/plugin/hoster/XdadevelopersCom.py
new file mode 100644
index 000000000..040057cd5
--- /dev/null
+++ b/pyload/plugin/hoster/XdadevelopersCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*
+#
+# Test links:
+# http://forum.xda-developers.com/devdb/project/dl/?id=10885
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class XdadevelopersCom(SimpleHoster):
+ __name = "XdadevelopersCom"
+ __type = "hoster"
+ __version = "0.03"
+
+ __pattern = r'https?://(?:www\.)?forum\.xda-developers\.com/devdb/project/dl/\?id=\d+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Xda-developers.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zapp-brannigan", "fuerst.reinje@web.de")]
+
+
+ NAME_PATTERN = r'<label>Filename:</label>\s*<div>\s*(?P<N>.*?)\n'
+ SIZE_PATTERN = r'<label>Size:</label>\s*<div>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'</i> Device Filter</h3>'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ self.download(pyfile.url,
+ get={'task': "get"})
diff --git a/pyload/plugin/hoster/Xdcc.py b/pyload/plugin/hoster/Xdcc.py
new file mode 100644
index 000000000..85f416aba
--- /dev/null
+++ b/pyload/plugin/hoster/Xdcc.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+
+import re
+import select
+import socket
+import struct
+import sys
+import time
+
+from pyload.plugin.Hoster import Hoster
+from pyload.utils import fs_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"""
+ __license = "GPLv3"
+ __authors = [("jeix", "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.getClassName(), type="XDCC")
+
+ self.pyfile = pyfile
+ for _i in xrange(0, 3):
+ try:
+ nmn = self.doDownload(pyfile.url)
+ self.logDebug("Download of %s finished." % nmn)
+ return
+ except socket.error, e:
+ if hasattr(e, "errno"):
+ errno = e.errno
+ else:
+ errno = e.args[0]
+
+ if errno == 10054:
+ self.logDebug("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))
+
+ self.setWait(3)
+ self.wait()
+
+ 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.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("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.get("general", "download_folder")
+ filename = fs_join(download_folder, packname)
+
+ self.logInfo(_("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/plugin/hoster/YadiSk.py b/pyload/plugin/hoster/YadiSk.py
new file mode 100644
index 000000000..96ca04cff
--- /dev/null
+++ b/pyload/plugin/hoster/YadiSk.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+from pyload.utils import json_loads
+
+
+class YadiSk(SimpleHoster):
+ __name = "YadiSk"
+ __type = "hoster"
+ __version = "0.05"
+
+ __pattern = r'https?://yadi\.sk/d/[\w-]+'
+
+ __description = """Yadi.sk hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("GammaC0de", None)]
+
+
+ OFFLINE_PATTERN = r'Nothing found'
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = super(YadiSk, cls).getInfo(url, html)
+
+ if html:
+ if 'idclient' not in info:
+ info['idclient'] = ""
+ for _i in xrange(32):
+ info ['idclient'] += random.choice('0123456abcdef')
+
+ m = re.search(r'<script id="models-client" type="application/json">(.+?)</script>', html)
+ if m:
+ api_data = json_loads(m.group(1))
+ try:
+ for sect in api_data:
+ if 'model' in sect:
+ if sect['model'] == "config":
+ info['version'] = sect['data']['version']
+ info['sk'] = sect['data']['sk']
+
+ elif sect['model'] == "resource":
+ info['id'] = sect['data']['id']
+ info['size'] = sect['data']['meta']['size']
+ info['name'] = sect['data']['name']
+
+ except Exception, e:
+ info['status'] = 8
+ info['error'] = _("Unexpected server response: %s") % e.message
+
+ else:
+ info['status'] = 8
+ info['error'] = _("could not find required json data")
+
+ return info
+
+
+ def setup(self):
+ self.resumeDownload = False
+ self.multiDL = False
+ self.chunkLimit = 1
+
+
+ def handle_free(self, pyfile):
+ if any(True for _k in ['id', 'sk', 'version', 'idclient'] if _k not in self.info):
+ self.error(_("Missing JSON data"))
+
+
+ try:
+ self.html = self.load("https://yadi.sk/models/",
+ get={'_m': "do-get-resource-url"},
+ post={'idClient': self.info['idclient'],
+ 'version' : self.info['version'],
+ '_model.0': "do-get-resource-url",
+ 'sk' : self.info['sk'],
+ 'id.0' : self.info['id']})
+
+ self.link = json_loads(self.html)['models'][0]['data']['file']
+
+ except Exception:
+ pass
diff --git a/pyload/plugin/hoster/YibaishiwuCom.py b/pyload/plugin/hoster/YibaishiwuCom.py
new file mode 100644
index 000000000..a6256bf6b
--- /dev/null
+++ b/pyload/plugin/hoster/YibaishiwuCom.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class YibaishiwuCom(SimpleHoster):
+ __name = "YibaishiwuCom"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'http://(?:www\.)?(?:u\.)?115\.com/file/(?P<ID>\w+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """115.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ NAME_PATTERN = r'file_name: \'(?P<N>.+?)\''
+ SIZE_PATTERN = r'file_size: \'(?P<S>.+?)\''
+ OFFLINE_PATTERN = ur'<h3><i style="color:red;">哎呀提取码䞍存圚䞍劚搜搜看吧</i></h3>'
+
+ LINK_FREE_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)'
+
+
+ def handle_free(self, pyfile):
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_FREE_PATTERN not found"))
+
+ url = m.group(1)
+
+ self.logDebug(('FREEUSER' if m.group(2) == 'download' else 'GUEST') + ' URL', url)
+
+ res = json_loads(self.load("http://115.com" + url, decode=False))
+ if "urls" in res:
+ mirrors = res['urls']
+
+ elif "data" in res:
+ mirrors = res['data']
+
+ else:
+ mirrors = None
+
+ for mr in mirrors:
+ try:
+ self.link = mr['url'].replace("\\", "")
+ self.logDebug("Trying URL: " + self.link)
+ break
+ except Exception:
+ continue
+ else:
+ self.fail(_("No working link found"))
diff --git a/pyload/plugin/hoster/YoupornCom.py b/pyload/plugin/hoster/YoupornCom.py
new file mode 100644
index 000000000..980211864
--- /dev/null
+++ b/pyload/plugin/hoster/YoupornCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Hoster import Hoster
+
+
+class YoupornCom(Hoster):
+ __name = "YoupornCom"
+ __type = "hoster"
+ __version = "0.20"
+
+ __pattern = r'http://(?:www\.)?youporn\.com/watch/.+'
+
+ __description = """Youporn.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("willnix", "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>(.+) - '
+ 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):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/YourfilesTo.py b/pyload/plugin/hoster/YourfilesTo.py
new file mode 100644
index 000000000..62b66d668
--- /dev/null
+++ b/pyload/plugin/hoster/YourfilesTo.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.Hoster import Hoster
+
+
+class YourfilesTo(Hoster):
+ __name = "YourfilesTo"
+ __type = "hoster"
+ __version = "0.22"
+
+ __pattern = r'http://(?:www\.)?yourfiles\.(to|biz)/\?d=\w+'
+
+ __description = """Youfiles.to hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("jeix", "jeix@hasnomail.de"),
+ ("skydancer", "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.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 = urllib.unquote(url.replace("http://http:/http://", "http://").replace("dumdidum", ""))
+ return url
+ else:
+ self.error(_("Absolute filepath not found"))
+
+
+ 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):
+ return False
+ else:
+ return True
diff --git a/pyload/plugin/hoster/YoutubeCom.py b/pyload/plugin/hoster/YoutubeCom.py
new file mode 100644
index 000000000..4998efbec
--- /dev/null
+++ b/pyload/plugin/hoster/YoutubeCom.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import subprocess
+import urllib
+
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.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"""
+
+ isExe = lambda x: os.path.isfile(x) and os.access(x, os.X_OK)
+
+ fpath, fname = os.path.split(program)
+
+ if fpath:
+ if isExe(program):
+ return program
+ else:
+ for path in os.environ['PATH'].split(os.pathsep):
+ path = path.strip('"')
+ exe_file = os.path.join(path, program)
+ if isExe(exe_file):
+ return exe_file
+
+
+class YoutubeCom(Hoster):
+ __name = "YoutubeCom"
+ __type = "hoster"
+ __version = "0.41"
+
+ __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 (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"""
+ __license = "GPLv3"
+ __authors = [("spoob", "spoob@pyload.org"),
+ ("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ 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 = True
+ self.multiDL = True
+
+
+ def process(self, pyfile):
+ pyfile.url = replace_patterns(pyfile.url, self.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 not desired_fmt:
+ desired_fmt = quality.get(self.getConfig('quality'), 18)
+
+ elif desired_fmt not in self.formats:
+ self.logWarning(_("FMT %d unknown, using default") % desired_fmt)
+ desired_fmt = 0
+
+ # 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']), urllib.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/plugin/hoster/ZDF.py b/pyload/plugin/hoster/ZDF.py
new file mode 100644
index 000000000..8272cfd93
--- /dev/null
+++ b/pyload/plugin/hoster/ZDF.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import re
+import xml
+
+from pyload.plugin.Hoster import Hoster
+
+
+# Based on zdfm by Roland Beermann (http://github.com/enkore/zdfm/)
+class ZDF(Hoster):
+ __name = "ZDF Mediathek"
+ __type = "hoster"
+ __version = "0.80"
+
+ __pattern = r'http://(?:www\.)?zdf\.de/ZDFmediathek/\D*(\d+)\D*'
+
+ __description = """ZDF.de hoster plugin"""
+ __license = "GPLv3"
+ __authors = []
+
+ 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"\D*(\d{4,})\D*", url).group(1))
+
+
+ def process(self, pyfile):
+ xml = xml.etree.ElementTree.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/plugin/hoster/ZShareNet.py b/pyload/plugin/hoster/ZShareNet.py
new file mode 100644
index 000000000..a2461fb6a
--- /dev/null
+++ b/pyload/plugin/hoster/ZShareNet.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.DeadHoster import DeadHoster
+
+
+class ZShareNet(DeadHoster):
+ __name = "ZShareNet"
+ __type = "hoster"
+ __version = "0.21"
+
+ __pattern = r'https?://(?:ww[2w]\.)?zshares?\.net/.+'
+ __config = []
+
+ __description = """ZShare.net hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("espes", ""),
+ ("Cptn Sandwich", "")]
diff --git a/pyload/plugin/hoster/ZeveraCom.py b/pyload/plugin/hoster/ZeveraCom.py
new file mode 100644
index 000000000..29e4da223
--- /dev/null
+++ b/pyload/plugin/hoster/ZeveraCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.MultiHoster import MultiHoster
+
+
+class ZeveraCom(MultiHoster):
+ __name = "ZeveraCom"
+ __type = "hoster"
+ __version = "0.29"
+
+ __pattern = r'https?://(?:www\.)zevera\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Zevera.com multi-hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ def handle_premium(self, pyfile):
+ self.link = "https://%s/getFiles.ashx?ourl=%s" % (self.account.HOSTER_DOMAIN, pyfile.url)
+
+
+ def checkFile(self, rules={}):
+ if self.checkDownload({"error": 'action="ErrorDownload.aspx'}):
+ self.fail(_("Error response received"))
+
+ return super(ZeveraCom, self).checkFile(rules)
diff --git a/pyload/plugin/hoster/ZippyshareCom.py b/pyload/plugin/hoster/ZippyshareCom.py
new file mode 100644
index 000000000..df9af062b
--- /dev/null
+++ b/pyload/plugin/hoster/ZippyshareCom.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+import BeautifulSoup
+
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.internal.SimpleHoster import SimpleHoster
+
+
+class ZippyshareCom(SimpleHoster):
+ __name = "ZippyshareCom"
+ __type = "hoster"
+ __version = "0.78"
+
+ __pattern = r'http://www\d{0,2}\.zippyshare\.com/v(/|iew\.jsp.*key=)(?P<KEY>[\w^_]+)'
+ __config = [("use_premium", "bool", "Use premium account if available", True)]
+
+ __description = """Zippyshare.com hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com"),
+ ("sebdelsol", "seb.morin@gmail.com")]
+
+
+ COOKIES = [("zippyshare.com", "ziplocale", "en")]
+
+ NAME_PATTERN = r'(<title>Zippyshare.com - |"/)(?P<N>[^/]+)(</title>|";)'
+ SIZE_PATTERN = r'>Size:.+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)'
+ OFFLINE_PATTERN = r'does not exist (anymore )?on this server<'
+
+ LINK_PREMIUM_PATTERN = r"document.location = '(.+?)'"
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.multiDL = True
+ self.resumeDownload = True
+
+
+ def handle_free(self, pyfile):
+ recaptcha = ReCaptcha(self)
+ captcha_key = recaptcha.detect_key()
+
+ if captcha_key:
+ try:
+ self.link = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ recaptcha.challenge()
+
+ except Exception, e:
+ self.error(e)
+
+ else:
+ self.link = self.get_link()
+
+ if self.link and pyfile.name == 'file.html':
+ pyfile.name = urllib.unquote(self.link.split('/')[-1])
+
+
+ def get_link(self):
+ # get all the scripts inside the html body
+ soup = BeautifulSoup.BeautifulSoup(self.html)
+ scripts = (s.getText().strip() for s in soup.body.findAll('script', type='text/javascript'))
+
+ # meant to be populated with the initialization of all the DOM elements found in the scripts
+ initScripts = set()
+
+
+ def replElementById(element):
+ id = element.group(1) #: id might be either 'x' (a real id) or x (a variable)
+ attr = element.group(4) #: attr might be None
+
+ varName = re.sub(r'-', '', 'GVAR[%s+"_%s"]' %(id, attr))
+
+ realid = id.strip('"\'')
+ if id != realid: #: id is not a variable, so look for realid.attr in the html
+ initValues = filter(None, [elt.get(attr, None) for elt in soup.findAll(id=realid)])
+ initValue = '"%s"' % initValues[-1] if initValues else 'null'
+ initScripts.add('%s = %s;' % (varName, initValue))
+
+ return varName
+
+ # handle all getElementById
+ reVar = r'document.getElementById\(([\'"\w-]+)\)(\.)?(getAttribute\([\'"])?(\w+)?([\'"]\))?'
+ scripts = [re.sub(reVar, replElementById, script) for script in scripts if script]
+
+ # add try/catch in JS to handle deliberate errors
+ scripts = ['\n'.join(('try{', script, '} catch(err){}')) for script in scripts]
+
+ # get the file's url by evaluating all the scripts
+ scripts = ['var GVAR = {}'] + list(initScripts) + scripts + ['GVAR['dlbutton_href']']
+ return self.js.eval('\n'.join(scripts))
diff --git a/pyload/plugin/hoster/__init__.py b/pyload/plugin/hoster/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/hoster/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/internal/BasePlugin.py b/pyload/plugin/internal/BasePlugin.py
new file mode 100644
index 000000000..6be9880fc
--- /dev/null
+++ b/pyload/plugin/internal/BasePlugin.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+import urlparse
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugin.internal.SimpleHoster import getFileURL
+from pyload.plugin.Hoster import Hoster
+
+
+class BasePlugin(Hoster):
+ __name = "BasePlugin"
+ __type = "hoster"
+ __version = "0.43"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """Base plugin when any other didnt fit"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "RaNaN@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""): #@TODO: Move to hoster class in 0.4.10
+ url = urllib.unquote(url)
+ url_p = urlparse.urlparse(url)
+ return {'name' : (url_p.path.split('/')[-1]
+ or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
+ or url_p.netloc.split('.', 1)[0]),
+ 'size' : 0,
+ 'status': 3 if url else 8,
+ 'url' : url}
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.multiDL = True
+ self.resumeDownload = True
+ def process(self, pyfile):
+ """main function"""
+
+ pyfile.name = self.getInfo(pyfile.url)['name']
+
+ if not pyfile.url.startswith("http"):
+ self.fail(_("No plugin matched"))
+
+ for _i in xrange(5):
+ try:
+ link = getFileURL(self, urllib.unquote(pyfile.url))
+
+ if link:
+ self.download(link, ref=False, disposition=True)
+ else:
+ self.fail(_("File not found"))
+
+ except BadHeader, e:
+ if e.code is 404:
+ self.offline()
+
+ elif e.code in (401, 403):
+ self.logDebug("Auth required", "Received HTTP status code: %d" % e.code)
+
+ account = self.core.accountManager.getAccountPlugin('Http')
+ servers = [x['login'] for x in account.getAllAccounts()]
+ server = urlparse.urlparse(pyfile.url).netloc
+
+ if server in servers:
+ self.logDebug("Logging on to %s" % server)
+ self.req.addAuth(account.getAccountData(server)['password'])
+ else:
+ pwd = self.getPassword()
+ if ':' in pwd:
+ self.req.addAuth(pwd)
+ else:
+ self.fail(_("Authorization required"))
+ else:
+ self.fail(e)
+ else:
+ break
+ else:
+ self.fail(_("No file downloaded")) #@TODO: Move to hoster class in 0.4.10
+
+ errmsg = self.checkDownload({'Empty file' : re.compile(r'\A\s*\Z'),
+ 'Html error' : re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)'),
+ 'Html file' : re.compile(r'\A\s*<!DOCTYPE html'),
+ 'Request error': re.compile(r'([Aa]n error occured while processing your request)')})
+ if not errmsg:
+ return
+
+ try:
+ errmsg += " | " + self.lastCheck.group(1).strip()
+ except Exception:
+ pass
+
+ self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
+ self.retry(3, 60, errmsg)
diff --git a/pyload/plugin/internal/DeadCrypter.py b/pyload/plugin/internal/DeadCrypter.py
new file mode 100644
index 000000000..daa7e1a0d
--- /dev/null
+++ b/pyload/plugin/internal/DeadCrypter.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Crypter import Crypter as _Crypter
+
+
+class DeadCrypter(_Crypter):
+ __name = "DeadCrypter"
+ __type = "crypter"
+ __version = "0.04"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """Crypter is no longer available"""
+ __license = "GPLv3"
+ __authors = [("stickell", "l.stickell@yahoo.it")]
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ api = super(DeadCrypter, self).apiInfo(url, get, post)
+ api['status'] = 1
+ return api
+
+
+ def setup(self):
+ self.pyfile.error = "Crypter is no longer available"
+ self.offline() #@TODO: self.offline("Crypter is no longer available")
diff --git a/pyload/plugin/internal/DeadHoster.py b/pyload/plugin/internal/DeadHoster.py
new file mode 100644
index 000000000..2e57decdb
--- /dev/null
+++ b/pyload/plugin/internal/DeadHoster.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.Hoster import Hoster as _Hoster
+
+
+class DeadHoster(_Hoster):
+ __name = "DeadHoster"
+ __type = "hoster"
+ __version = "0.14"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """Hoster is no longer available"""
+ __license = "GPLv3"
+ __authors = [("zoidberg", "zoidberg@mujmail.cz")]
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ api = super(DeadHoster, self).apiInfo(url, get, post)
+ api['status'] = 1
+ return api
+
+
+ def setup(self):
+ self.pyfile.error = "Hoster is no longer available"
+ self.offline() #@TODO: self.offline("Hoster is no longer available")
diff --git a/pyload/plugin/internal/MultiHook.py b/pyload/plugin/internal/MultiHook.py
new file mode 100644
index 000000000..3779377b7
--- /dev/null
+++ b/pyload/plugin/internal/MultiHook.py
@@ -0,0 +1,276 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import traceback
+
+from pyload.plugin.Hook import Hook
+from pyload.utils import decode, remove_chars
+
+
+class MultiHook(Hook):
+ __name = "MultiHook"
+ __type = "hook"
+ __version = "0.45"
+
+ __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"),
+ ("pluginlist" , "str" , "Plugin list (comma separated)", ""),
+ ("reload" , "bool" , "Reload plugin list" , True),
+ ("reloadinterval", "int" , "Reload interval in hours" , 12)]
+
+ __description = """Hook plugin for multi hoster/crypter"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team" , "admin@pyload.org"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ MIN_RELOAD_INTERVAL = 1 * 60 * 60 #: 1 hour
+
+ DOMAIN_REPLACEMENTS = [(r'180upload\.com' , "hundredeightyupload.com"),
+ (r'bayfiles\.net' , "bayfiles.com"),
+ (r'cloudnator\.com' , "shragle.com"),
+ (r'dfiles\.eu' , "depositfiles.com"),
+ (r'easy-share\.com' , "crocko.com"),
+ (r'freakshare\.net' , "freakshare.com"),
+ (r'hellshare\.com' , "hellshare.cz"),
+ (r'ifile\.it' , "filecloud.io"),
+ (r'nowdownload\.\w+', "nowdownload.sx"),
+ (r'nowvideo\.\w+' , "nowvideo.sx"),
+ (r'putlocker\.com' , "firedrive.com"),
+ (r'share-?rapid\.cz', "multishare.cz"),
+ (r'ul\.to' , "uploaded.to"),
+ (r'uploaded\.net' , "uploaded.to"),
+ (r'uploadhero\.co' , "uploadhero.com"),
+ (r'zshares\.net' , "zshare.net"),
+ (r'^1' , "one"),
+ (r'^2' , "two"),
+ (r'^3' , "three"),
+ (r'^4' , "four"),
+ (r'^5' , "five"),
+ (r'^6' , "six"),
+ (r'^7' , "seven"),
+ (r'^8' , "eight"),
+ (r'^9' , "nine"),
+ (r'^0' , "zero")]
+
+
+ def setup(self):
+ self.plugins = []
+ self.supported = []
+ self.new_supported = []
+
+ self.account = None
+ self.pluginclass = None
+ self.pluginmodule = None
+ self.pluginname = None
+ self.plugintype = None
+
+ self.initPlugin()
+
+
+ def initPlugin(self):
+ self.pluginname = self.getClassName()
+ plugin, self.plugintype = self.core.pluginManager.findPlugin(("hoster", "crypter", "container"), self.pluginname)
+
+ if plugin:
+ self.pluginmodule = self.core.pluginManager.loadModule(self.plugintype, self.pluginname)
+ self.pluginclass = getattr(self.pluginmodule, self.pluginname)
+ else:
+ self.logWarning("Hook plugin will be deactivated due missing plugin reference")
+ self.setConfig('activated', False)
+
+
+ def loadAccount(self):
+ self.account = self.core.accountManager.getAccountPlugin(self.pluginname)
+
+ if self.account and not self.account.canUse():
+ self.account = None
+
+ if not self.account and hasattr(self.pluginclass, "LOGIN_ACCOUNT") and self.pluginclass.LOGIN_ACCOUNT:
+ self.logWarning("Hook plugin will be deactivated due missing account reference")
+ self.setConfig('activated', False)
+
+
+ def activate(self):
+ self.initPeriodical(threaded=True)
+
+
+ def getURL(self, *args, **kwargs): #@TODO: Remove in 0.4.10
+ """ see HTTPRequest for argument list """
+ h = pyreq.getHTTPRequest(timeout=120)
+ try:
+ if not 'decode' in kwargs:
+ kwargs['decode'] = True
+ rep = h.load(*args, **kwargs)
+ finally:
+ h.close()
+
+ return rep
+
+
+ def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
+ """getConfig with default value - sublass may not implements all config options"""
+ try:
+ return self.getConf(option)
+
+ except KeyError:
+ return default
+
+
+ def pluginsCached(self):
+ if self.plugins:
+ return self.plugins
+
+ for _i in xrange(2):
+ try:
+ pluginset = self._pluginSet(self.getHosters())
+ break
+
+ except Exception, e:
+ self.logDebug(e, "Waiting 1 minute and retry")
+ time.sleep(60)
+ else:
+ self.logWarning(_("Fallback to default reload interval due plugin parse error"))
+ self.interval = self.MIN_RELOAD_INTERVAL
+ return list()
+
+ try:
+ configmode = self.getConfig('pluginmode', 'all')
+ if configmode in ("listed", "unlisted"):
+ pluginlist = self.getConfig('pluginlist', '').replace('|', ',').replace(';', ',').split(',')
+ configset = self._pluginSet(pluginlist)
+
+ if configmode == "listed":
+ pluginset &= configset
+ else:
+ pluginset -= configset
+
+ except Exception, e:
+ self.logError(e)
+
+ self.plugins = list(pluginset)
+
+ return self.plugins
+
+
+ def _pluginSet(self, plugins):
+ regexp = re.compile(r'^[\w\-.^_]{3,63}\.[a-zA-Z]{2,}$', re.U)
+ plugins = [decode(p.strip()).lower() for p in plugins if regexp.match(p.strip())]
+
+ for r in self.DOMAIN_REPLACEMENTS:
+ rf, rt = r
+ repr = re.compile(rf, re.I | re.U)
+ plugins = [re.sub(rf, rt, p) if repr.match(p) else p for p in plugins]
+
+ return set(plugins)
+
+
+ def getHosters(self):
+ """Load list of supported hoster
+
+ :return: List of domain names
+ """
+ raise NotImplementedError
+
+
+ def periodical(self):
+ """reload plugin list periodically"""
+ self.loadAccount()
+
+ if self.getConfig('reload', True):
+ self.interval = max(self.getConfig('reloadinterval', 12) * 60 * 60, self.MIN_RELOAD_INTERVAL)
+ else:
+ self.core.scheduler.removeJob(self.cb)
+ self.cb = None
+
+ self.logInfo(_("Reloading supported %s list") % self.plugintype)
+
+ old_supported = self.supported
+
+ self.supported = []
+ self.new_supported = []
+ self.plugins = []
+
+ self.overridePlugins()
+
+ old_supported = [plugin for plugin in old_supported if plugin not in self.supported]
+
+ if old_supported:
+ self.logDebug("Unload: %s" % ", ".join(old_supported))
+ for plugin in old_supported:
+ self.unloadPlugin(plugin)
+
+
+ def overridePlugins(self):
+ excludedList = []
+
+ if self.plugintype == "hoster":
+ pluginMap = dict((name.lower(), name) for name in self.core.pluginManager.hosterPlugins.iterkeys())
+ accountList = [account.type.lower() for account in self.core.api.getAccounts(False) if account.valid and account.premium]
+ else:
+ pluginMap = {}
+ accountList = [name[::-1].replace("Folder"[::-1], "", 1).lower()[::-1] for name in self.core.pluginManager.crypterPlugins.iterkeys()]
+
+ for plugin in self.pluginsCached():
+ name = remove_chars(plugin, "-.")
+
+ if name in accountList:
+ excludedList.append(plugin)
+ else:
+ if name in pluginMap:
+ self.supported.append(pluginMap[name])
+ else:
+ self.new_supported.append(plugin)
+
+ if not self.supported and not self.new_supported:
+ self.logError(_("No %s loaded") % self.plugintype)
+ return
+
+ # inject plugin plugin
+ self.logDebug("Overwritten %ss: %s" % (self.plugintype, ", ".join(sorted(self.supported))))
+
+ for plugin in self.supported:
+ hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
+ hdict['new_module'] = self.pluginmodule
+ hdict['new_name'] = self.pluginname
+
+ if excludedList:
+ self.logInfo(_("%ss not overwritten: %s") % (self.plugintype.capitalize(), ", ".join(sorted(excludedList))))
+
+ if self.new_supported:
+ plugins = sorted(self.new_supported)
+
+ self.logDebug("New %ss: %s" % (self.plugintype, ", ".join(plugins)))
+
+ # create new regexp
+ regexp = r'.*(?P<DOMAIN>%s).*' % "|".join(x.replace('.', '\.') for x in plugins)
+ if hasattr(self.pluginclass, "__pattern") and isinstance(self.pluginclass.__pattern, basestring) and '://' in self.pluginclass.__pattern:
+ regexp = r'%s|%s' % (self.pluginclass.__pattern, regexp)
+
+ self.logDebug("Regexp: %s" % regexp)
+
+ hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
+ hdict['pattern'] = regexp
+ hdict['re'] = re.compile(regexp)
+
+
+ def unloadPlugin(self, plugin):
+ hdict = self.core.pluginManager.plugins[self.plugintype][plugin]
+ if "module" in hdict:
+ hdict.pop('module', None)
+
+ if "new_module" in hdict:
+ hdict.pop('new_module', None)
+ hdict.pop('new_name', None)
+
+
+ def deactivate(self):
+ """Remove override for all plugins. Scheduler job is removed by hookmanager"""
+ for plugin in self.supported:
+ self.unloadPlugin(plugin)
+
+ # reset pattern
+ hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname]
+
+ hdict['pattern'] = getattr(self.pluginclass, "__pattern", r'^unmatchable$')
+ hdict['re'] = re.compile(hdict['pattern'])
diff --git a/pyload/plugin/internal/MultiHoster.py b/pyload/plugin/internal/MultiHoster.py
new file mode 100644
index 000000000..06d78475b
--- /dev/null
+++ b/pyload/plugin/internal/MultiHoster.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugin.Plugin import Fail, Retry
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns, set_cookies
+
+
+class MultiHoster(SimpleHoster):
+ __name = "MultiHoster"
+ __type = "hoster"
+ __version = "0.39"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium" , "bool", "Use premium account if available" , True),
+ ("revertfailed", "bool", "Revert to standard download if fails", True)]
+
+ __description = """Multi hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ LOGIN_ACCOUNT = True
+
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.multiDL = bool(self.account)
+ self.resumeDownload = self.premium
+
+
+ def prepare(self):
+ self.info = {}
+ self.html = ""
+ self.link = "" #@TODO: Move to hoster class in 0.4.10
+ self.directDL = False #@TODO: Move to hoster class in 0.4.10
+
+ if not self.getConfig('use_premium', True):
+ self.retryFree()
+
+ if self.LOGIN_ACCOUNT and not self.account:
+ self.fail(_("Required account not found"))
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+ if self.DIRECT_LINK is None:
+ self.directDL = self.__pattern != r'^unmatchable$' and re.match(self.__pattern, self.pyfile.url)
+ else:
+ self.directDL = self.DIRECT_LINK
+
+ self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
+
+
+ def process(self, pyfile):
+ try:
+ self.prepare()
+
+ if self.directDL:
+ self.checkInfo()
+ self.logDebug("Looking for direct download link...")
+ self.handle_direct(pyfile)
+
+ if not self.link and not self.lastDownload:
+ self.preload()
+
+ self.checkErrors()
+ self.checkStatus(getinfo=False)
+
+ if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as premium download")
+ self.handle_premium(pyfile)
+
+ elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as free download")
+ self.handle_free(pyfile)
+
+ self.downloadLink(self.link, True)
+ self.checkFile()
+
+ except Fail, e: #@TODO: Move to PluginThread in 0.4.10
+ if self.premium:
+ self.logWarning(_("Premium download failed"))
+ self.retryFree()
+
+ elif self.getConfig('revertfailed', True) \
+ and "new_module" in self.core.pluginManager.hosterPlugins[self.getClassName()]:
+ hdict = self.core.pluginManager.hosterPlugins[self.getClassName()]
+
+ tmp_module = hdict['new_module']
+ tmp_name = hdict['new_name']
+ hdict.pop('new_module', None)
+ hdict.pop('new_name', None)
+
+ pyfile.initPlugin()
+
+ hdict['new_module'] = tmp_module
+ hdict['new_name'] = tmp_name
+
+ raise Retry(_("Revert to original hoster plugin"))
+
+ else:
+ raise Fail(e)
+
+
+ def handle_premium(self, pyfile):
+ return self.handle_free(pyfile)
+
+
+ def handle_free(self, pyfile):
+ if self.premium:
+ raise NotImplementedError
+ else:
+ self.fail(_("Required premium account not found"))
diff --git a/pyload/plugin/internal/SimpleCrypter.py b/pyload/plugin/internal/SimpleCrypter.py
new file mode 100644
index 000000000..dff543eed
--- /dev/null
+++ b/pyload/plugin/internal/SimpleCrypter.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns, set_cookies
+from pyload.utils import fixup
+
+
+class SimpleCrypter(Crypter, SimpleHoster):
+ __name = "SimpleCrypter"
+ __type = "crypter"
+ __version = "0.43"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), #: Overrides core.config.get("general", "folder_per_package")
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Simple decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ """
+ Following patterns should be defined by each crypter:
+
+ LINK_PATTERN: Download link or regex to catch links in group(1)
+ example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
+
+ NAME_PATTERN: (optional) folder name or page title
+ example: NAME_PATTERN = r'<title>Files of: (?P<N>[^<]+) folder</title>'
+
+ OFFLINE_PATTERN: (optional) Checks if the page is unreachable
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
+ example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
+
+
+ You can override the getLinks method if you need a more sophisticated way to extract the links.
+
+
+ If the links are splitted on multiple pages you can define the PAGES_PATTERN regex:
+
+ PAGES_PATTERN: (optional) group(1) should be the number of overall pages containing the links
+ example: PAGES_PATTERN = r'Pages: (\d+)'
+
+ and its loadPage method:
+
+ def loadPage(self, page_n):
+ return the html of the page number page_n
+ """
+
+ LINK_PATTERN = None
+
+ NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
+ URL_REPLACEMENTS = []
+
+ TEXT_ENCODING = False #: Set to True or encoding name if encoding in http header is not correct
+ COOKIES = True #: or False or list of tuples [(domain, name, value)]
+
+ LOGIN_ACCOUNT = False
+ LOGIN_PREMIUM = False
+
+
+ def prepare(self):
+ self.pyfile.error = "" #@TODO: Remove in 0.4.10
+
+ self.info = {}
+ self.html = ""
+ self.links = [] #@TODO: Move to hoster class in 0.4.10
+
+ if self.LOGIN_PREMIUM and not self.premium:
+ self.fail(_("Required premium account not found"))
+
+ if self.LOGIN_ACCOUNT and not self.account:
+ self.fail(_("Required account not found"))
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+ self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
+
+
+ def decrypt(self, pyfile):
+ self.prepare()
+
+ self.preload()
+ self.checkInfo()
+
+ self.links = self.getLinks()
+
+ if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'):
+ self.handle_pages(pyfile)
+
+ self.logDebug("Package has %d links" % len(self.links))
+
+ if self.links:
+ self.packages = [(self.info['name'], self.links, self.info['folder'])]
+
+ elif not self.urls and not self.packages: #@TODO: Remove in 0.4.10
+ self.fail(_("No link grabbed"))
+
+
+ def checkNameSize(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("File info (BEFORE): %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("File info (AFTER): %s" % self.info)
+
+ try:
+ url = self.info['url'].strip()
+ name = self.info['name'].strip()
+ if name and name != url:
+ self.pyfile.name = name
+
+ except Exception:
+ pass
+
+ try:
+ folder = self.info['folder'] = self.pyfile.name
+
+ except Exception:
+ pass
+
+ self.logDebug("File name: %s" % self.pyfile.name,
+ "File folder: %s" % self.pyfile.name)
+
+
+ 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.
+ """
+ url_p = urlparse.urlparse(self.pyfile.url)
+ baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
+
+ return [urlparse.urljoin(baseurl, link) if not urlparse.urlparse(link).scheme else link \
+ for link in re.findall(self.LINK_PATTERN, self.html)]
+
+
+ def handle_pages(self, pyfile):
+ try:
+ pages = int(re.search(self.PAGES_PATTERN, self.html).group(1))
+ except Exception:
+ pages = 1
+
+ for p in xrange(2, pages + 1):
+ self.html = self.loadPage(p)
+ self.links += self.getLinks()
diff --git a/pyload/plugin/internal/SimpleDereferer.py b/pyload/plugin/internal/SimpleDereferer.py
new file mode 100644
index 000000000..0ec947751
--- /dev/null
+++ b/pyload/plugin/internal/SimpleDereferer.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urllib
+
+from pyload.plugin.Crypter import Crypter
+from pyload.plugin.internal.SimpleHoster import getFileURL, set_cookies
+
+
+class SimpleDereferer(Crypter):
+ __name = "SimpleDereferer"
+ __type = "crypter"
+ __version = "0.11"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_subfolder" , "bool", "Save package to subfolder" , True),
+ ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)]
+
+ __description = """Simple dereferer plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ """
+ Following patterns should be defined by each crypter:
+
+ LINK_PATTERN: Regex to catch the redirect url in group(1)
+ example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"'
+
+ OFFLINE_PATTERN: (optional) Checks if the page is unreachable
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable
+ example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
+
+
+ You can override the getLinks method if you need a more sophisticated way to extract the redirect url.
+ """
+
+ LINK_PATTERN = None
+
+ TEXT_ENCODING = False
+ COOKIES = True
+
+
+ def decrypt(self, pyfile):
+ link = getFileURL(self, pyfile.url)
+
+ if not link:
+ try:
+ link = urllib.unquote(re.match(self.__pattern, pyfile.url).group('LINK'))
+
+ except Exception:
+ self.prepare()
+ self.preload()
+ self.checkStatus()
+
+ link = self.getLink()
+
+ if link.strip():
+ self.urls = [link]
+
+
+ def prepare(self):
+ self.info = {}
+ self.html = ""
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+
+ def preload(self):
+ self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
+
+ if isinstance(self.TEXT_ENCODING, basestring):
+ self.html = unicode(self.html, self.TEXT_ENCODING)
+
+
+ def checkStatus(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 getLink(self):
+ try:
+ return re.search(self.LINK_PATTERN, self.html).group(1)
+
+ except Exception:
+ pass
diff --git a/pyload/plugin/internal/SimpleHoster.py b/pyload/plugin/internal/SimpleHoster.py
new file mode 100644
index 000000000..7ba7db8da
--- /dev/null
+++ b/pyload/plugin/internal/SimpleHoster.py
@@ -0,0 +1,758 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+import mimetypes
+import os
+import re
+import time
+import urllib
+import urlparse
+
+from pyload.datatype.File import statusMap as _statusMap
+from pyload.network.CookieJar import CookieJar
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL
+from pyload.plugin.Hoster import Hoster
+from pyload.plugin.Plugin import Fail, Retry
+from pyload.utils import fixup, fs_encode, parseFileSize
+
+
+#@TODO: Adapt and move to PyFile in 0.4.10
+statusMap = dict((v, k) for k, v in _statusMap.iteritems())
+
+
+#@TODO: Remove in 0.4.10 and redirect to self.error instead
+def _error(self, reason, type):
+ if not reason and not type:
+ type = "unknown"
+
+ msg = _("%s error") % type.strip().capitalize() if type else _("Error")
+ msg += (": %s" % reason.strip()) if reason else ""
+ msg += _(" | Plugin may be out of date")
+
+ raise Fail(msg)
+
+
+#@TODO: Remove in 0.4.10
+def _wait(self, seconds, reconnect):
+ if seconds:
+ self.setWait(int(seconds) + 1)
+
+ if reconnect is not None:
+ self.wantReconnect = reconnect
+
+ super(SimpleHoster, self).wait()
+
+
+def replace_patterns(string, ruleslist):
+ for r in ruleslist:
+ rf, rt = r
+ string = re.sub(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={}):
+ 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 input_names:
+ # check input attributes
+ for key, val in input_names.iteritems():
+ 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
+
+
+#: Deprecated
+def parseFileInfo(plugin, url="", html=""):
+ if hasattr(plugin, "getInfo"):
+ info = plugin.getInfo(url, html)
+ res = info['name'], info['size'], info['status'], info['url']
+ else:
+ url = urllib.unquote(url)
+ url_p = urlparse.urlparse(url)
+ res = ((url_p.path.split('/')[-1]
+ or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
+ or url_p.netloc.split('.', 1)[0]),
+ 0,
+ 3 if url else 8,
+ url)
+
+ return res
+
+
+#@TODO: Remove in 0.4.10
+#@NOTE: Every plugin must have own parseInfos classmethod to work with 0.4.10
+# def create_getInfo(plugin):
+
+ # def generator(list):
+ # for x in list:
+ # yield x
+
+ # if hasattr(plugin, "parseInfos"):
+ # fn = lambda urls: generator((info['name'], info['size'], info['status'], info['url']) for info in plugin.parseInfos(urls))
+ # else:
+ # fn = lambda urls: generator(parseFileInfo(url) for url in urls)
+
+ # return fn
+
+
+def timestamp():
+ return int(time.time() * 1000)
+
+
+#@TODO: Move to hoster class in 0.4.10
+def getFileURL(self, url, follow_location=None):
+ link = ""
+ redirect = 1
+
+ if type(follow_location) is int:
+ redirect = max(follow_location, 1)
+ else:
+ redirect = 10
+
+ for i in xrange(redirect):
+ try:
+ self.logDebug("Redirect #%d to: %s" % (i, url))
+ header = self.load(url, just_header=True, decode=True)
+
+ except Exception: #: Bad bad bad...
+ req = pyreq.getHTTPRequest()
+ res = req.load(url, just_header=True, decode=True)
+
+ req.close()
+
+ header = {"code": 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
+
+ if 'content-disposition' in header:
+ link = url
+
+ elif 'location' in header and header['location'].strip():
+ location = header['location']
+
+ if not urlparse.urlparse(location).scheme:
+ url_p = urlparse.urlparse(url)
+ baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
+ location = urlparse.urljoin(baseurl, location)
+
+ if 'code' in header and header['code'] == 302:
+ link = location
+
+ if follow_location:
+ url = location
+ continue
+
+ else:
+ extension = os.path.splitext(urlparse.urlparse(url).path.split('/')[-1])[-1]
+
+ if 'content-type' in header and header['content-type'].strip():
+ mimetype = header['content-type'].split(';')[0].strip()
+
+ elif extension:
+ mimetype = mimetypes.guess_type(extension, False)[0] or "application/octet-stream"
+
+ else:
+ mimetype = ""
+
+ if mimetype and (link or 'html' not in mimetype):
+ link = url
+ else:
+ link = ""
+
+ break
+
+ else:
+ try:
+ self.logError(_("Too many redirects"))
+ except Exception:
+ pass
+
+ return link
+
+
+def secondsToMidnight(gmt=0):
+ now = datetime.datetime.utcnow() + datetime.timedelta(hours=gmt)
+
+ if now.hour is 0 and now.minute < 10:
+ midnight = now
+ else:
+ midnight = now + datetime.timedelta(days=1)
+
+ td = midnight.replace(hour=0, minute=10, second=0, microsecond=0) - now
+
+ if hasattr(td, 'total_seconds'):
+ res = td.total_seconds()
+ else: #: work-around for python 2.5 and 2.6 missing datetime.timedelta.total_seconds
+ res = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6
+
+ return int(res)
+
+
+class SimpleHoster(Hoster):
+ __name = "SimpleHoster"
+ __type = "hoster"
+ __version = "1.42"
+
+ __pattern = r'^unmatchable$'
+ __config = [("use_premium", "bool", "Use premium account if available" , True),
+ ("fallback" , "bool", "Fallback to free download if premium fails", True)]
+
+ __description = """Simple hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+ """
+ Info patterns should be defined by each hoster:
+
+ INFO_PATTERN: (optional) Name and Size of the file
+ example: INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)'
+ or
+ NAME_PATTERN: (optional) Name that will be set for the file
+ example: NAME_PATTERN = r'(?P<N>file_name)'
+ SIZE_PATTERN: (optional) Size that will be checked for the file
+ example: SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)'
+
+ HASHSUM_PATTERN: (optional) Hash code and type of the file
+ example: HASHSUM_PATTERN = r'(?P<H>hash_code) (?P<T>MD5)'
+
+ OFFLINE_PATTERN: (optional) Check if the page is unreachable
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Check if the page is temporarily unreachable
+ example: TEMP_OFFLINE_PATTERN = r'Server (maintenance|maintainance)'
+
+
+ Error handling patterns are all optional:
+
+ WAIT_PATTERN: (optional) Detect waiting time
+ example: WAIT_PATTERN = r''
+
+ PREMIUM_ONLY_PATTERN: (optional) Check if the file can be downloaded only with a premium account
+ example: PREMIUM_ONLY_PATTERN = r'Premium account required'
+
+ ERROR_PATTERN: (optional) Detect any error preventing download
+ example: ERROR_PATTERN = r''
+
+
+ Instead overriding handle_free and handle_premium methods you can define the following patterns for direct download:
+
+ LINK_FREE_PATTERN: (optional) group(1) should be the direct link for free download
+ example: LINK_FREE_PATTERN = r'<div class="link"><a href="(.+?)"'
+
+ LINK_PREMIUM_PATTERN: (optional) group(1) should be the direct link for premium download
+ example: LINK_PREMIUM_PATTERN = r'<div class="link"><a href="(.+?)"'
+ """
+
+ NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
+ SIZE_REPLACEMENTS = []
+ URL_REPLACEMENTS = []
+
+ TEXT_ENCODING = False #: Set to True or encoding name if encoding value in http header is not correct
+ COOKIES = True #: or False or list of tuples [(domain, name, value)]
+ CHECK_TRAFFIC = False #: Set to True to force checking traffic left for premium account
+ DIRECT_LINK = None #: Set to True to looking for direct link (as defined in handle_direct method), set to None to do it if self.account is True else False
+ MULTI_HOSTER = False #: Set to True to leech other hoster link (as defined in handle_multi method)
+ LOGIN_ACCOUNT = False #: Set to True to require account login
+ DISPOSITION = True #: Set to True to use any content-disposition value in http header as file name
+
+ directLink = getFileURL #@TODO: Remove in 0.4.10
+
+
+ @classmethod
+ def parseInfos(cls, urls): #@TODO: Built-in in 0.4.10 core (remove from plugins)
+ for url in urls:
+ url = replace_patterns(url, cls.URL_REPLACEMENTS)
+ yield cls.getInfo(url)
+
+
+ @classmethod
+ def apiInfo(cls, url="", get={}, post={}):
+ url = urllib.unquote(url)
+ url_p = urlparse.urlparse(url)
+ return {'name': (url_p.path.split('/')[-1]
+ or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0]
+ or url_p.netloc.split('.', 1)[0]),
+ 'size': 0,
+ 'status': 3 if url else 8,
+ 'url': url}
+
+
+ @classmethod
+ def getInfo(cls, url="", html=""):
+ info = cls.apiInfo(url)
+ online = info['status'] == 2
+
+ try:
+ info['pattern'] = re.match(cls.__pattern, url).groupdict() #: pattern groups will be saved here
+
+ except Exception:
+ info['pattern'] = {}
+
+ if not html and not online:
+ if not url:
+ info['error'] = "missing url"
+ info['status'] = 1
+
+ elif info['status'] is 3 and not getFileURL(None, url):
+ try:
+ html = getURL(url, cookies=cls.COOKIES, decode=not cls.TEXT_ENCODING)
+
+ if isinstance(cls.TEXT_ENCODING, basestring):
+ html = unicode(html, cls.TEXT_ENCODING)
+
+ except BadHeader, e:
+ info['error'] = "%d: %s" % (e.code, e.content)
+
+ if e.code is 404:
+ info['status'] = 1
+
+ elif e.code is 503:
+ info['status'] = 6
+
+ if html:
+ if hasattr(cls, "OFFLINE_PATTERN") and re.search(cls.OFFLINE_PATTERN, html):
+ info['status'] = 1
+
+ elif hasattr(cls, "TEMP_OFFLINE_PATTERN") and re.search(cls.TEMP_OFFLINE_PATTERN, html):
+ info['status'] = 6
+
+ else:
+ for pattern in ("INFO_PATTERN", "NAME_PATTERN", "SIZE_PATTERN", "HASHSUM_PATTERN"):
+ try:
+ attr = getattr(cls, pattern)
+ pdict = re.search(attr, html).groupdict()
+
+ if all(True for k in pdict if k not in info['pattern']):
+ info['pattern'].update(pdict)
+
+ except AttributeError:
+ continue
+
+ else:
+ online = True
+
+ if online:
+ info['status'] = 2
+
+ if 'N' in info['pattern']:
+ info['name'] = replace_patterns(urllib.unquote(info['pattern']['N'].strip()),
+ cls.NAME_REPLACEMENTS)
+
+ if 'S' in info['pattern']:
+ size = replace_patterns(info['pattern']['S'] + info['pattern']['U'] if 'U' in info['pattern'] else info['pattern']['S'],
+ cls.SIZE_REPLACEMENTS)
+ info['size'] = parseFileSize(size)
+
+ elif isinstance(info['size'], basestring):
+ unit = info['units'] if 'units' in info else None
+ info['size'] = parseFileSize(info['size'], unit)
+
+ if 'H' in info['pattern']:
+ hashtype = info['pattern']['T'] if 'T' in info['pattern'] else "hash"
+ info[hashtype] = info['pattern']['H']
+
+ if not info['pattern']:
+ info.pop('pattern', None)
+
+ return info
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+
+ def prepare(self):
+ self.pyfile.error = "" #@TODO: Remove in 0.4.10
+
+ self.info = {}
+ self.html = ""
+ self.link = "" #@TODO: Move to hoster class in 0.4.10
+ self.directDL = False #@TODO: Move to hoster class in 0.4.10
+ self.multihost = False #@TODO: Move to hoster class in 0.4.10
+
+ if not self.getConfig('use_premium', True):
+ self.retryFree()
+
+ if self.LOGIN_ACCOUNT and not self.account:
+ self.fail(_("Required account not found"))
+
+ self.req.setOption("timeout", 120)
+
+ if isinstance(self.COOKIES, list):
+ set_cookies(self.req.cj, self.COOKIES)
+
+ if (self.MULTI_HOSTER
+ and (self.__pattern != self.core.pluginManager.hosterPlugins[self.getClassName()]['pattern']
+ or re.match(self.__pattern, self.pyfile.url) is None)):
+ self.multihost = True
+ return
+
+ if self.DIRECT_LINK is None:
+ self.directDL = bool(self.account)
+ else:
+ self.directDL = self.DIRECT_LINK
+
+ self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS)
+
+
+ def preload(self):
+ self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING)
+
+ if isinstance(self.TEXT_ENCODING, basestring):
+ self.html = unicode(self.html, self.TEXT_ENCODING)
+
+
+ def process(self, pyfile):
+ try:
+ self.prepare()
+ self.checkInfo()
+
+ if self.directDL:
+ self.logDebug("Looking for direct download link...")
+ self.handle_direct(pyfile)
+
+ if self.multihost and not self.link and not self.lastDownload:
+ self.logDebug("Looking for leeched download link...")
+ self.handle_multi(pyfile)
+
+ if not self.link and not self.lastDownload:
+ self.MULTI_HOSTER = False
+ self.retry(1, reason="Multi hoster fails")
+
+ if not self.link and not self.lastDownload:
+ self.preload()
+ self.checkInfo()
+
+ if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as premium download")
+ self.handle_premium(pyfile)
+
+ elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.logDebug("Handled as free download")
+ self.handle_free(pyfile)
+
+ self.downloadLink(self.link, self.DISPOSITION)
+ self.checkFile()
+
+ except Fail, e: #@TODO: Move to PluginThread in 0.4.10
+ if self.getConfig('fallback', True) and self.premium:
+ self.logWarning(_("Premium download failed"), e)
+ self.retryFree()
+ else:
+ raise Fail(e)
+ def downloadLink(self, link, disposition=True):
+ if link and isinstance(link, basestring):
+ self.correctCaptcha()
+
+ if not urlparse.urlparse(link).scheme:
+ url_p = urlparse.urlparse(self.pyfile.url)
+ baseurl = "%s://%s" % (url_p.scheme, url_p.netloc)
+ link = urlparse.urljoin(baseurl, link)
+
+ self.download(link, ref=False, disposition=disposition)
+
+
+ def checkFile(self, rules={}):
+ if self.cTask and not self.lastDownload:
+ self.invalidCaptcha()
+ self.retry(10, reason=_("Wrong captcha"))
+
+ elif not self.lastDownload or not os.path.exists(fs_encode(self.lastDownload)):
+ self.lastDownload = ""
+ self.error(self.pyfile.error or _("No file downloaded"))
+
+ else:
+ errmsg = self.checkDownload({'Empty file': re.compile(r'\A\s*\Z'),
+ 'Html error': re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)')})
+
+ if not errmsg:
+ for r, p in [('Html file', re.compile(r'\A\s*<!DOCTYPE html')),
+ ('Request error', re.compile(r'([Aa]n error occured while processing your request)'))]:
+ if r not in rules:
+ rules[r] = p
+
+ for r, a in [('Error', "ERROR_PATTERN"),
+ ('Premium only', "PREMIUM_ONLY_PATTERN"),
+ ('Wait error', "WAIT_PATTERN")]:
+ if r not in rules and hasattr(self, a):
+ rules[r] = getattr(self, a)
+
+ errmsg = self.checkDownload(rules)
+
+ if not errmsg:
+ return
+
+ errmsg = errmsg.strip().capitalize()
+
+ try:
+ errmsg += " | " + self.lastCheck.group(1).strip()
+ except Exception:
+ pass
+
+ self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry")
+ self.retry(3, 60, errmsg)
+
+
+ def checkErrors(self):
+ if not self.html:
+ self.logWarning(_("No html code to check"))
+ return
+
+ if hasattr(self, 'PREMIUM_ONLY_PATTERN') and not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html):
+ self.fail(_("Link require a premium account to be handled"))
+
+ elif hasattr(self, 'ERROR_PATTERN'):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ try:
+ errmsg = m.group(1).strip()
+ except Exception:
+ errmsg = m.group(0).strip()
+
+ self.info['error'] = errmsg
+
+ if "hour" in errmsg:
+ self.wait(1 * 60 * 60, True)
+
+ elif re.search("da(il)?y|today", errmsg):
+ self.wait(secondsToMidnight(gmt=2), True)
+
+ elif "minute" in errmsg:
+ self.wait(1 * 60)
+
+ else:
+ self.error(errmsg)
+
+ elif hasattr(self, 'WAIT_PATTERN'):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ try:
+ waitmsg = m.group(1).strip()
+ except Exception:
+ waitmsg = m.group(0).strip()
+
+ wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
+ re.findall(r'(\d+)\s*(hr|hour|min|sec|)', waitmsg, re.I))
+ self.wait(wait_time, wait_time > 300)
+
+ self.info.pop('error', None)
+
+
+ def checkStatus(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("Update file info...")
+ self.logDebug("Previous file info: %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("Current file info: %s" % self.info)
+
+ try:
+ status = self.info['status']
+
+ if status is 1:
+ self.offline()
+
+ elif status is 6:
+ self.tempOffline()
+
+ elif status is 8:
+ self.fail(self.info['error'] if 'error' in self.info else "Failed")
+
+ finally:
+ self.logDebug("File status: %s" % statusMap[status])
+
+
+ def checkNameSize(self, getinfo=True):
+ if not self.info or getinfo:
+ self.logDebug("Update file info...")
+ self.logDebug("Previous file info: %s" % self.info)
+ self.info.update(self.getInfo(self.pyfile.url, self.html))
+ self.logDebug("Current file info: %s" % self.info)
+
+ try:
+ url = self.info['url'].strip()
+ name = self.info['name'].strip()
+ if name and name != url:
+ self.pyfile.name = name
+
+ except Exception:
+ pass
+
+ try:
+ size = self.info['size']
+ if size > 0:
+ self.pyfile.size = size
+
+ except Exception:
+ pass
+
+ self.logDebug("File name: %s" % self.pyfile.name,
+ "File size: %s byte" % self.pyfile.size if self.pyfile.size > 0 else "File size: Unknown")
+
+
+ def checkInfo(self):
+ self.checkNameSize()
+
+ if self.html:
+ self.checkErrors()
+ self.checkNameSize()
+
+ self.checkStatus(getinfo=False)
+
+
+ #: Deprecated
+ def getFileInfo(self):
+ self.info = {}
+ self.checkInfo()
+ return self.info
+
+
+ def handle_direct(self, pyfile):
+ link = self.directLink(pyfile.url, self.resumeDownload)
+
+ if link:
+ self.logInfo(_("Direct download link detected"))
+ self.link = link
+ else:
+ self.logDebug("Direct download link not found")
+
+
+ def handle_multi(self, pyfile): #: Multi-hoster handler
+ pass
+
+
+ def handle_free(self, pyfile):
+ if not hasattr(self, 'LINK_FREE_PATTERN'):
+ self.logError(_("Free download not implemented"))
+
+ m = re.search(self.LINK_FREE_PATTERN, self.html)
+ if m is None:
+ self.error(_("Free download link not found"))
+ else:
+ self.link = m.group(1)
+
+
+ def handle_premium(self, pyfile):
+ if not hasattr(self, 'LINK_PREMIUM_PATTERN'):
+ self.logError(_("Premium download not implemented"))
+ self.logDebug("Handled as free download")
+ self.handle_free(pyfile)
+
+ m = re.search(self.LINK_PREMIUM_PATTERN, self.html)
+ if m is None:
+ self.error(_("Premium download link not found"))
+ else:
+ self.link = m.group(1)
+
+
+ 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.wait(wait_time, True)
+ self.retry(max_tries=max_tries, reason=_("Download limit reached"))
+
+
+ def parseHtmlForm(self, attr_str="", input_names={}):
+ return parseHtmlForm(attr_str, self.html, input_names)
+
+
+ def checkTrafficLeft(self):
+ if not self.account:
+ return True
+
+ traffic = self.account.getAccountInfo(self.user, True)['trafficleft']
+
+ if traffic is None:
+ return False
+ elif traffic == -1:
+ return True
+ else:
+ size = self.pyfile.size
+ self.logInfo(_("Filesize: %i KiB, Traffic left for user %s: %i KiB") % (size / 1024, self.user, traffic / 1024))
+ return size <= traffic
+
+
+ def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10
+ """getConfig with default value - sublass may not implements all config options"""
+ try:
+ return self.getConf(option)
+
+ except KeyError:
+ return default
+
+
+ def retryFree(self):
+ if not self.premium:
+ return
+ self.premium = False
+ self.account = None
+ self.req = self.core.requestFactory.getRequest(self.getClassName())
+ self.retries = -1
+ raise Retry(_("Fallback to free download"))
+
+
+ def wait(self, seconds=0, reconnect=None):
+ return _wait(self, seconds, reconnect)
+
+
+ def error(self, reason="", type="parse"):
+ return _error(self, reason, type)
diff --git a/pyload/plugin/internal/XFSAccount.py b/pyload/plugin/internal/XFSAccount.py
new file mode 100644
index 000000000..105df3cd5
--- /dev/null
+++ b/pyload/plugin/internal/XFSAccount.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+import urlparse
+
+from pyload.plugin.Account import Account
+from pyload.plugin.internal.SimpleHoster import parseHtmlForm, set_cookies
+
+
+class XFSAccount(Account):
+ __name = "XFSAccount"
+ __type = "account"
+ __version = "0.36"
+
+ __description = """XFileSharing account plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("Walter Purcaro", "vuolter@gmail.com" )]
+
+
+ HOSTER_DOMAIN = None
+ HOSTER_URL = None
+ LOGIN_URL = None
+
+ COOKIES = True
+
+ PREMIUM_PATTERN = r'\(Premium only\)'
+
+ VALID_UNTIL_PATTERN = r'Premium.[Aa]ccount expire:.*?(\d{1,2} [\w^_]+ \d{4})'
+
+ TRAFFIC_LEFT_PATTERN = r'Traffic available today:.*?<b>\s*(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
+ TRAFFIC_LEFT_UNIT = "MB" #: used only if no group <U> was found
+
+ LEECH_TRAFFIC_PATTERN = r'Leech Traffic left:<b>.*?(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>'
+ LEECH_TRAFFIC_UNIT = "MB" #: used only if no group <U> was found
+
+ LOGIN_FAIL_PATTERN = r'Incorrect Login or Password|account was banned|Error<'
+
+
+ def init(self):
+ if not self.HOSTER_DOMAIN:
+ self.logError(_("Missing HOSTER_DOMAIN"))
+ self.COOKIES = False
+
+ else:
+ if not self.HOSTER_URL:
+ self.HOSTER_URL = "http://www.%s/" % self.HOSTER_DOMAIN
+
+ if isinstance(self.COOKIES, list):
+ self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
+ set_cookies(req.cj, self.COOKIES)
+
+
+ def loadAccountInfo(self, user, req):
+ validuntil = None
+ trafficleft = None
+ leechtraffic = None
+ premium = None
+
+ if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
+ return {'validuntil' : validuntil,
+ 'trafficleft' : trafficleft,
+ 'leechtraffic': leechtraffic,
+ 'premium' : premium}
+
+ html = req.load(self.HOSTER_URL, get={'op': "my_account"}, decode=True)
+
+ premium = re.search(self.PREMIUM_PATTERN, html) is not None
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ expiredate = m.group(1).strip()
+ self.logDebug("Expire date: " + expiredate)
+
+ try:
+ validuntil = time.mktime(time.strptime(expiredate, "%d %B %Y"))
+
+ except Exception, e:
+ self.logError(e)
+
+ else:
+ self.logDebug("Valid until: %s" % validuntil)
+
+ if validuntil > time.mktime(time.gmtime()):
+ premium = True
+ trafficleft = -1
+ else:
+ premium = False
+ validuntil = None #: registered account type (not premium)
+ else:
+ self.logDebug("VALID_UNTIL_PATTERN not found")
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ try:
+ traffic = m.groupdict()
+ size = traffic['S']
+
+ if "nlimited" in size:
+ trafficleft = -1
+ if validuntil is None:
+ validuntil = -1
+ else:
+ if 'U' in traffic:
+ unit = traffic['U']
+ elif isinstance(self.TRAFFIC_LEFT_UNIT, basestring):
+ unit = self.TRAFFIC_LEFT_UNIT
+ else:
+ unit = ""
+
+ trafficleft = self.parseTraffic(size + unit)
+
+ except Exception, e:
+ self.logError(e)
+ else:
+ self.logDebug("TRAFFIC_LEFT_PATTERN not found")
+
+ leech = [m.groupdict() for m in re.finditer(self.LEECH_TRAFFIC_PATTERN, html)]
+ if leech:
+ leechtraffic = 0
+ try:
+ for traffic in leech:
+ size = traffic['S']
+
+ if "nlimited" in size:
+ leechtraffic = -1
+ if validuntil is None:
+ validuntil = -1
+ break
+ else:
+ if 'U' in traffic:
+ unit = traffic['U']
+ elif isinstance(self.LEECH_TRAFFIC_UNIT, basestring):
+ unit = self.LEECH_TRAFFIC_UNIT
+ else:
+ unit = ""
+
+ leechtraffic += self.parseTraffic(size + unit)
+
+ except Exception, e:
+ self.logError(e)
+ else:
+ self.logDebug("LEECH_TRAFFIC_PATTERN not found")
+
+ return {'validuntil' : validuntil,
+ 'trafficleft' : trafficleft,
+ 'leechtraffic': leechtraffic,
+ 'premium' : premium}
+
+
+ def login(self, user, data, req):
+ if not self.HOSTER_URL: #@TODO: Remove in 0.4.10
+ raise Exception(_("Missing HOSTER_DOMAIN"))
+
+ if not self.LOGIN_URL:
+ self.LOGIN_URL = urlparse.urljoin(self.HOSTER_URL, "login.html")
+ html = req.load(self.LOGIN_URL, decode=True)
+
+ action, inputs = parseHtmlForm('name="FL"', html)
+ if not inputs:
+ inputs = {'op' : "login",
+ 'redirect': self.HOSTER_URL}
+
+ inputs.update({'login' : user,
+ 'password': data['password']})
+
+ if not action:
+ action = self.HOSTER_URL
+ html = req.load(action, post=inputs, decode=True)
+
+ if re.search(self.LOGIN_FAIL_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugin/internal/XFSCrypter.py b/pyload/plugin/internal/XFSCrypter.py
new file mode 100644
index 000000000..e8dfa7e39
--- /dev/null
+++ b/pyload/plugin/internal/XFSCrypter.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.internal.SimpleCrypter import SimpleCrypter
+
+
+class XFSCrypter(SimpleCrypter):
+ __name = "XFSCrypter"
+ __type = "crypter"
+ __version = "0.09"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """XFileSharing decrypter plugin"""
+ __license = "GPLv3"
+ __authors = [("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+
+ URL_REPLACEMENTS = [(r'&?per_page=\d+', ""), (r'[?/&]+$', ""), (r'(.+/[^?]+)$', r'\1?'), (r'$', r'&per_page=10000')]
+
+ LINK_PATTERN = r'<(?:td|TD).*?>\s*(?:<.+>\s*)?<a href="(.+?)".*?>.+?(?:</a>)?\s*(?:<.+>\s*)?</(?:td|TD)>'
+ NAME_PATTERN = r'<[Tt]itle>.*?\: (?P<N>.+) folder</[Tt]itle>'
+
+ OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
+ TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
+
+
+ def prepare(self):
+ if not self.HOSTER_DOMAIN:
+ if self.account:
+ account = self.account
+ else:
+ account_name = (self.getClassName() + ".py").replace("Folder.py", "").replace(".py", "")
+ account = self.pyfile.m.core.accountManager.getAccountPlugin(account_name)
+
+ if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
+ self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
+ else:
+ self.fail(_("Missing HOSTER_DOMAIN"))
+
+ if isinstance(self.COOKIES, list):
+ self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
+
+ return super(XFSCrypter, self).prepare()
diff --git a/pyload/plugin/internal/XFSHoster.py b/pyload/plugin/internal/XFSHoster.py
new file mode 100644
index 000000000..0a2dc4fbb
--- /dev/null
+++ b/pyload/plugin/internal/XFSHoster.py
@@ -0,0 +1,317 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+from pyload.plugin.captcha.ReCaptcha import ReCaptcha
+from pyload.plugin.captcha.SolveMedia import SolveMedia
+from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight
+from pyload.utils import html_unescape
+
+
+class XFSHoster(SimpleHoster):
+ __name = "XFSHoster"
+ __type = "hoster"
+ __version = "0.47"
+
+ __pattern = r'^unmatchable$'
+
+ __description = """XFileSharing hoster plugin"""
+ __license = "GPLv3"
+ __authors = [("zoidberg" , "zoidberg@mujmail.cz"),
+ ("stickell" , "l.stickell@yahoo.it"),
+ ("Walter Purcaro", "vuolter@gmail.com")]
+
+
+ HOSTER_DOMAIN = None
+
+ TEXT_ENCODING = False
+ DIRECT_LINK = None
+ MULTI_HOSTER = True #@NOTE: Should be default to False for safe, but I'm lazy...
+
+ NAME_PATTERN = r'(Filename[ ]*:[ ]*</b>(</td><td nowrap>)?|name="fname"[ ]+value="|<[\w^_]+ class="(file)?name">)\s*(?P<N>.+?)(\s*<|")'
+ SIZE_PATTERN = r'(Size[ ]*:[ ]*</b>(</td><td>)?|File:.*>|</font>\s*\(|<[\w^_]+ class="size">)\s*(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)'
+
+ OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)'
+ TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)'
+
+ WAIT_PATTERN = r'<span id="countdown_str".*>(\d+)</span>|id="countdown" value=".*?(\d+).*?"'
+ PREMIUM_ONLY_PATTERN = r'>This file is available for Premium Users only'
+ ERROR_PATTERN = r'(?:class=["\']err["\'].*?>|<[Cc]enter><b>|>Error</td>|>\(ERROR:)(?:\s*<.+?>\s*)*(.+?)(?:["\']|<|\))'
+
+ LINK_LEECH_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
+ LINK_PATTERN = None #: final download url pattern
+
+ CAPTCHA_PATTERN = r'(https?://[^"\']+?/captchas?/[^"\']+)'
+ CAPTCHA_BLOCK_PATTERN = r'>Enter code.*?<div.*?>(.+?)</div>'
+ RECAPTCHA_PATTERN = None
+ SOLVEMEDIA_PATTERN = None
+
+ FORM_PATTERN = None
+ FORM_INPUTS_MAP = None #: dict passed as input_names to parseHtmlForm
+
+
+ def setup(self):
+ self.chunkLimit = -1 if self.premium else 1
+ self.resumeDownload = self.multiDL = self.premium
+
+
+ def prepare(self):
+ """ Initialize important variables """
+ if not self.HOSTER_DOMAIN:
+ if self.account:
+ account = self.account
+ else:
+ account = self.pyfile.m.core.accountManager.getAccountPlugin(self.getClassName())
+
+ if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN:
+ self.HOSTER_DOMAIN = account.HOSTER_DOMAIN
+ else:
+ self.fail(_("Missing HOSTER_DOMAIN"))
+
+ if isinstance(self.COOKIES, list):
+ self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english"))
+
+ if not self.LINK_PATTERN:
+ pattern = r'(https?://(?:www\.)?([^/]*?%s|\d+\.\d+\.\d+\.\d+)(\:\d+)?(/d/|(/files)?/\d+/\w+/).+?)["\'<]'
+ self.LINK_PATTERN = pattern % self.HOSTER_DOMAIN.replace('.', '\.')
+
+ self.captcha = None
+ self.errmsg = None
+
+ super(XFSHoster, self).prepare()
+
+ if self.DIRECT_LINK is None:
+ self.directDL = self.premium
+
+
+ def handle_free(self, pyfile):
+ for i in xrange(1, 6):
+ self.logDebug("Getting download link: #%d" % i)
+
+ self.checkErrors()
+
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ break
+
+ data = self.getPostParameters()
+
+ self.html = self.load(pyfile.url, post=data, ref=True, decode=True, follow_location=False)
+
+ m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I)
+ if m and not "op=" in m.group(1):
+ break
+
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ break
+ else:
+ self.logError(data['op'] if 'op' in data else _("UNKNOWN"))
+ return ""
+
+ self.link = m.group(1)
+
+
+ def handle_premium(self, pyfile):
+ return self.handle_free(pyfile)
+
+
+ def handle_multi(self, pyfile):
+ if not self.account:
+ self.fail(_("Only registered or premium users can use url leech feature"))
+
+ # only tested with easybytez.com
+ self.html = self.load("http://www.%s/" % self.HOSTER_DOMAIN)
+
+ action, inputs = self.parseHtmlForm()
+
+ upload_id = "%012d" % int(random.random() * 10 ** 12)
+ action += upload_id + "&js_on=1&utype=prem&upload_type=url"
+
+ inputs['tos'] = '1'
+ inputs['url_mass'] = pyfile.url
+ inputs['up1oad_type'] = 'url'
+
+ self.logDebug(action, inputs)
+
+ self.req.setOption("timeout", 600) #: wait for file to upload to easybytez.com
+
+ self.html = self.load(action, post=inputs)
+
+ self.checkErrors()
+
+ action, inputs = self.parseHtmlForm('F1')
+ if not inputs:
+ self.retry(reason=self.errmsg or _("TEXTAREA F1 not found"))
+
+ self.logDebug(inputs)
+
+ stmsg = inputs['st']
+
+ if stmsg == 'OK':
+ self.html = self.load(action, post=inputs)
+
+ elif 'Can not leech file' in stmsg:
+ self.retry(20, 3 * 60, _("Can not leech file"))
+
+ elif 'today' in stmsg:
+ self.retry(wait_time=secondsToMidnight(gmt=2), reason=_("You've used all Leech traffic today"))
+
+ else:
+ self.fail(stmsg)
+
+ # get easybytez.com link for uploaded file
+ m = re.search(self.LINK_LEECH_PATTERN, self.html)
+ if m is None:
+ self.error(_("LINK_LEECH_PATTERN not found"))
+
+ header = self.load(m.group(1), just_header=True, decode=True)
+
+ if 'location' in header: #: Direct download link
+ self.link = header['location']
+
+
+ def checkErrors(self):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m is None:
+ self.errmsg = None
+ else:
+ self.errmsg = m.group(1).strip()
+
+ self.logWarning(re.sub(r"<.*?>", " ", self.errmsg))
+
+ if 'wait' in self.errmsg:
+ wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in
+ re.findall(r'(\d+)\s*(hr|hour|min|sec|)', self.errmsg, re.I))
+ self.wait(wait_time, wait_time > 300)
+
+ elif 'country' in self.errmsg:
+ self.fail(_("Downloads are disabled for your country"))
+
+ 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:
+ if 'day' in self.errmsg:
+ delay = secondsToMidnight(gmt=2)
+ retries = 3
+ else:
+ delay = 1 * 60 * 60
+ retries = 24
+
+ self.wantReconnect = True
+ self.retry(retries, delay, _("Download limit exceeded"))
+
+ elif 'countdown' in self.errmsg or 'Expired' in self.errmsg:
+ self.retry(reason=_("Link expired"))
+
+ elif 'maintenance' in self.errmsg or 'maintainance' in self.errmsg:
+ self.tempOffline()
+
+ elif 'up to' in self.errmsg:
+ self.fail(_("File too large for free download"))
+
+ else:
+ self.wantReconnect = True
+ self.retry(wait_time=60, reason=self.errmsg)
+
+ if self.errmsg:
+ self.info['error'] = self.errmsg
+ else:
+ self.info.pop('error', None)
+
+
+ def getPostParameters(self):
+ if self.FORM_PATTERN or self.FORM_INPUTS_MAP:
+ action, inputs = self.parseHtmlForm(self.FORM_PATTERN or "", self.FORM_INPUTS_MAP or {})
+ else:
+ action, inputs = self.parseHtmlForm(input_names={'op': re.compile(r'^download')})
+
+ if not inputs:
+ action, inputs = self.parseHtmlForm('F1')
+ if not inputs:
+ self.retry(reason=self.errmsg or _("TEXTAREA F1 not found"))
+
+ self.logDebug(inputs)
+
+ if 'op' in inputs:
+ if "password" in inputs:
+ password = self.getPassword()
+ if password:
+ inputs['password'] = password
+ else:
+ self.fail(_("Missing password"))
+
+ if not self.premium:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1))
+ self.setWait(wait_time, False)
+
+ self.captcha = self.handleCaptcha(inputs)
+
+ self.wait()
+ else:
+ inputs['referer'] = self.pyfile.url
+
+ if self.premium:
+ inputs['method_premium'] = "Premium Download"
+ inputs.pop('method_free', None)
+ else:
+ inputs['method_free'] = "Free Download"
+ inputs.pop('method_premium', None)
+
+ return inputs
+
+
+ def handleCaptcha(self, inputs):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_url = m.group(1)
+ inputs['code'] = self.decryptCaptcha(captcha_url)
+ return 1
+
+ m = re.search(self.CAPTCHA_BLOCK_PATTERN, self.html, re.S)
+ if m:
+ captcha_div = m.group(1)
+ numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
+
+ self.logDebug(captcha_div)
+
+ inputs['code'] = "".join(a[1] for a in sorted(numerals, key=lambda num: int(num[0])))
+
+ self.logDebug("Captcha code: %s" % inputs['code'], numerals)
+ return 2
+
+ recaptcha = ReCaptcha(self)
+ try:
+ captcha_key = re.search(self.RECAPTCHA_PATTERN, self.html).group(1)
+
+ except Exception:
+ captcha_key = recaptcha.detect_key()
+
+ else:
+ self.logDebug("ReCaptcha key: %s" % captcha_key)
+
+ if captcha_key:
+ inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge(captcha_key)
+ return 3
+
+ solvemedia = SolveMedia(self)
+ try:
+ captcha_key = re.search(self.SOLVEMEDIA_PATTERN, self.html).group(1)
+
+ except Exception:
+ captcha_key = solvemedia.detect_key()
+
+ else:
+ self.logDebug("SolveMedia key: %s" % captcha_key)
+
+ if captcha_key:
+ inputs['adcopy_response'], inputs['adcopy_challenge'] = solvemedia.challenge(captcha_key)
+ return 4
+
+ return 0
diff --git a/pyload/plugin/internal/__init__.py b/pyload/plugin/internal/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/internal/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/plugin/ocr/GigasizeCom.py b/pyload/plugin/ocr/GigasizeCom.py
new file mode 100644
index 000000000..0c15b03e7
--- /dev/null
+++ b/pyload/plugin/ocr/GigasizeCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.OCR import OCR
+
+
+class GigasizeCom(OCR):
+ __name = "GigasizeCom"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Gigasize.com ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "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/plugin/ocr/LinksaveIn.py b/pyload/plugin/ocr/LinksaveIn.py
new file mode 100644
index 000000000..44ab08592
--- /dev/null
+++ b/pyload/plugin/ocr/LinksaveIn.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+import glob
+import os
+
+from pyload.plugin.OCR import OCR
+
+
+class LinksaveIn(OCR):
+ __name = "LinksaveIn"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Linksave.in ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "admin@pyload.org")]
+
+
+ def __init__(self):
+ OCR.__init__(self)
+ self.data_dir = os.path.dirname(os.path.abspath(__file__)) + os.sep + "LinksaveIn" + os.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.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 Exception:
+ cstat[rgb_c] = 1
+ if rgb_bg == rgb_c:
+ stat[bgpath] += 1
+ max_p = 0
+ bg = ""
+ for bgpath, value in stat.iteritems():
+ 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/plugin/ocr/NetloadIn.py b/pyload/plugin/ocr/NetloadIn.py
new file mode 100644
index 000000000..57651428b
--- /dev/null
+++ b/pyload/plugin/ocr/NetloadIn.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.OCR import OCR
+
+
+class NetloadIn(OCR):
+ __name = "NetloadIn"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Netload.in ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("pyLoad Team", "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/plugin/ocr/ShareonlineBiz.py b/pyload/plugin/ocr/ShareonlineBiz.py
new file mode 100644
index 000000000..8efbdee35
--- /dev/null
+++ b/pyload/plugin/ocr/ShareonlineBiz.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugin.OCR import OCR
+
+
+class ShareonlineBiz(OCR):
+ __name = "ShareonlineBiz"
+ __type = "ocr"
+ __version = "0.11"
+
+ __description = """Shareonline.biz ocr plugin"""
+ __license = "GPLv3"
+ __authors = [("RaNaN", "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/plugin/ocr/__init__.py b/pyload/plugin/ocr/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/pyload/plugin/ocr/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/remote/ClickNLoadBackend.py b/pyload/remote/ClickNLoadBackend.py
new file mode 100644
index 000000000..ece2dc316
--- /dev/null
+++ b/pyload/remote/ClickNLoadBackend.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import BaseHTTPServer
+import base64
+import binascii
+import cgi
+import re
+import urllib
+
+try:
+ import Crypto
+except Exception:
+ pass
+
+from pyload.manager.Remote import BackendBase
+
+core = None
+js = None
+
+
+class ClickNLoadBackend(BackendBase):
+
+ def setup(self, host, port):
+ self.httpd = BaseHTTPServer.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(BaseHTTPServer.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 = cgi.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', 'ClickNLoad 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', 'ClickNLoad Package')
+ dlc = self.get_post('crypted').replace(" ", "+")
+
+ core.upload_container(package, dlc)
+
+
+ def addcrypted2(self):
+ package = self.get_post("source", "ClickNLoad Package")
+ crypted = self.get_post("crypted")
+ jk = self.get_post("jk")
+
+ crypted = base64.standard_b64decode(urllib.unquote(crypted.replace(" ", "+")))
+ jk = "%s f()" % jk
+ jk = js.eval(jk)
+ Key = binascii.unhexlify(jk)
+ IV = Key
+
+ obj = Crypto.Cipher.AES.new(Key, Crypto.Cipher.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..8b74d19ff
--- /dev/null
+++ b/pyload/remote/SocketBackend.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import SocketServer
+
+from pyload.manager.Remote 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..f9aac2243
--- /dev/null
+++ b/pyload/remote/ThriftBackend.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, mkaay
+
+import os
+
+import thrift
+
+from pyload.manager.Remote import BackendBase
+from pyload.remote.thriftbackend.Processor import Processor
+from pyload.remote.thriftbackend.Protocol import ProtocolFactory
+from pyload.remote.thriftbackend.Socket import ServerSocket
+from pyload.remote.thriftbackend.Transport import TransportFactory
+# from pyload.remote.thriftbackend.Transport import TransportFactoryCompressed
+
+
+class ThriftBackend(BackendBase):
+
+ def setup(self, host, port):
+ processor = Processor(self.core.api)
+
+ key = None
+ cert = None
+
+ if self.core.config.get("ssl", "activated"):
+ if os.path.exists(self.core.config.get("ssl", "cert")) and os.path.exists(self.core.config.get("ssl", "key")):
+ self.core.log.info(_("Using SSL ThriftBackend"))
+ key = self.core.config.get("ssl", "key")
+ cert = self.core.config.get("ssl", "cert")
+
+ transport = ServerSocket(port, host, key, cert)
+
+
+ # tfactory = TransportFactoryCompressed()
+ tfactory = TransportFactory()
+ pfactory = ProtocolFactory()
+
+ self.server = thrift.server.TServer.TThreadedServer(processor, transport, tfactory, pfactory)
+ # self.server = TNonblockingServer.TNonblockingServer(processor, transport, tfactory, pfactory)
+
+ # server = thrift.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..40a96afc6
--- /dev/null
+++ b/pyload/remote/socketbackend/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/remote/socketbackend/create_ttypes.py b/pyload/remote/socketbackend/create_ttypes.py
new file mode 100644
index 000000000..00752dc6b
--- /dev/null
+++ b/pyload/remote/socketbackend/create_ttypes.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import inspect
+import os
+import platform
+import sys
+
+sys.path.append(os.path.join(pypath, "pyload", "remote"))
+
+from pyload.remote.thriftbackend.thriftgen.pyload import ttypes
+from pyload.remote.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)
+
+
+ with open(os.path.join(pypath, "pyload", "api", "types.py"), "wb") as f:
+ 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(object):\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/thriftbackend/Processor.py b/pyload/remote/thriftbackend/Processor.py
new file mode 100644
index 000000000..204047e2f
--- /dev/null
+++ b/pyload/remote/thriftbackend/Processor.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+from pyload.remote.thriftbackend.thriftgen.pyload import Pyload
+
+
+class Processor(Pyload.Processor):
+
+ def __init__(self, *args, **kwargs):
+ Pyload.Processor.__init__(self, *args, **kwargs)
+ self.authenticated = {}
+
+
+ def process(self, iprot, oprot):
+ trans = oprot.trans
+ if trans not in self.authenticated:
+ self.authenticated[trans] = False
+ oldclose = trans.close
+
+
+ def wrap():
+ if self in self.authenticated:
+ del self.authenticated[trans]
+ oldclose()
+
+ trans.close = wrap
+ authenticated = self.authenticated[trans]
+ (name, type, seqid) = iprot.readMessageBegin()
+
+ # unknown method
+ if name not in self._processMap:
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ x = Pyload.TApplicationException(Pyload.TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % name)
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
+ # not logged in
+ elif not authenticated and not name == "login":
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ # 20 - Not logged in (in situ declared error code)
+ x = Pyload.TApplicationException(20, 'Not logged in')
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
+ elif not authenticated and name == "login":
+ args = Pyload.login_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = Pyload.login_result()
+ # api login
+ self.authenticated[trans] = self._handler.checkAuth(args.username, args.password, trans.remoteaddr[0])
+
+ result.success = bool(self.authenticated[trans])
+ oprot.writeMessageBegin("login", Pyload.TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ elif self._handler.isAuthorized(name, authenticated):
+ self._processMap[name](self, seqid, iprot, oprot)
+
+ else:
+ # no permission
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ # 21 - Not authorized
+ x = Pyload.TApplicationException(21, 'Not authorized')
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
+ return True
diff --git a/pyload/remote/thriftbackend/Protocol.py b/pyload/remote/thriftbackend/Protocol.py
new file mode 100644
index 000000000..3aefa167f
--- /dev/null
+++ b/pyload/remote/thriftbackend/Protocol.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+import thrift
+
+
+class Protocol(thrift.protocol.TBinaryProtocol.thrift.protocol.TBinaryProtocol):
+
+ def writeString(self, str):
+ try:
+ str = str.encode("utf8", "ignore")
+ except Exception:
+ 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 Exception:
+ pass
+
+ return str
+
+
+class ProtocolFactory(thrift.protocol.TBinaryProtocol.thrift.protocol.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..003439ba7
--- /dev/null
+++ b/pyload/remote/thriftbackend/Socket.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import socket
+import errno
+import time
+
+import thrift
+
+
+WantReadError = Exception #: overwritten when ssl is used
+
+
+class SecureSocketConnection(object):
+
+ 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:
+ time.sleep(0.1)
+ return self.send(buff)
+
+
+ def recv(self, buff):
+ try:
+ return self.__dict__['connection'].recv(buff)
+ except WantReadError:
+ time.sleep(0.1)
+ return self.recv(buff)
+
+
+class Socket(thrift.transport.TSocket.TSocket):
+
+ def __init__(self, host='localhost', port=7228, ssl=False):
+ thrift.transport.TSocket.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 thrift.transport.TSocket.TSocket::read()
+ # in lib/cpp/src/transport/thrift.transport.TSocket.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 thrift.transport.TSocket.TTransportException(type=thrift.transport.TSocket.TTransportException.END_OF_FILE, message='thrift.transport.TSocket.TSocket read 0 bytes')
+ return buff
+
+
+class ServerSocket(thrift.transport.TSocket.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..259b6eff6
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftClient.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+import socket
+import sys
+import traceback
+
+import thrift
+
+from pyload.remote.thriftbackend.Protocol import Protocol
+from pyload.remote.thriftbackend.Socket import Socket
+
+# modules should import ttypes from here, when want to avoid importing API
+from pyload.remote.thriftbackend.thriftgen.pyload import Pyload
+from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import *
+
+
+ConnectionClosed = thrift.transport.TTransport.TTransportException
+
+
+class WrongLogin(Exception):
+ pass
+
+
+class NoConnection(Exception):
+ pass
+
+
+class NoSSL(Exception):
+ pass
+
+
+class ThriftClient(object):
+
+ def __init__(self, host="localhost", port=7227, user="", password=""):
+
+ self.createConnection(host, port)
+ try:
+ self.transport.open()
+
+ except socket.error, e:
+ if e.args and e.args[0] in (111, 10061):
+ raise NoConnection
+ else:
+ traceback.print_exc()
+ raise NoConnection
+
+ try:
+ correct = self.client.login(user, password)
+
+ except socket.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:
+ traceback.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 = thrift.transport.TTransport.TBufferedTransport(self.socket)
+ # self.transport = thrift.transport.TZlibTransport.TZlibTransport(thrift.transport.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..b3d351c0b
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftTest.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+
+import getpass
+import os
+import platform
+import sys
+import time
+import xmlrpclib
+
+import thrift
+
+from pyload.remote.thriftbackend.Protocol import Protocol
+from pyload.remote.thriftbackend.Socket import Socket
+from pyload.remote.thriftbackend.thriftgen.pyload import Pyload
+from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import *
+
+
+def bench(f, *args, **kwargs):
+ s = time.time()
+ ret = [f(*args, **kwargs) for _i in xrange(0, 100)]
+ e = time.time()
+ try:
+ print "%s: %f s" % (f._Method__name, e-s)
+ except Exception:
+ print "%s: %f s" % (f.__name__, e-s)
+ return ret
+
+user = raw_input("user ")
+passwd = getpass.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 = thrift.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.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..9946edd85
--- /dev/null
+++ b/pyload/remote/thriftbackend/Transport.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import thrift
+
+
+class Transport(thrift.transport.TTransport.TBufferedTransport):
+ DEFAULT_BUFFER = 4096
+
+
+ def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
+ thrift.transport.TTransport.TBufferedTransport.__init__(self, trans, rbuf_size)
+ self.handle = trans.handle
+ self.remoteaddr = trans.handle.getpeername()
+
+
+class TransportCompressed(thrift.transport.TZlibTransport.TZlibTransport):
+ DEFAULT_BUFFER = 4096
+
+
+ def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
+ thrift.transport.TZlibTransport.TZlibTransport.__init__(self, trans, rbuf_size)
+ self.handle = trans.handle
+ self.remoteaddr = trans.handle.getpeername()
+
+
+class TransportFactory(object):
+
+ def getTransport(self, trans):
+ buffered = Transport(trans)
+ return buffered
+
+
+class TransportFactoryCompressed(object):
+ _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..40a96afc6
--- /dev/null
+++ b/pyload/remote/thriftbackend/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
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..40a96afc6
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote
new file mode 100644
index 000000000..beab520e2
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote
@@ -0,0 +1,571 @@
+#
+# 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
+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.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..1ba11dbb6
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py
@@ -0,0 +1,5976 @@
+#
+# 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..e0a811c8a
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py
@@ -0,0 +1,11 @@
+#
+# 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..8abd775a9
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py
@@ -0,0 +1,860 @@
+#
+# 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/__init__.py b/pyload/utils/__init__.py
new file mode 100644
index 000000000..3c525caeb
--- /dev/null
+++ b/pyload/utils/__init__.py
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+""" Store all useful functions here """
+
+import bitmath
+import htmlentitydefs
+import os
+import re
+import string
+import sys
+import time
+import urllib
+
+import pyload.utils.pylgettext as gettext
+
+# abstraction layer for json operations
+from bottle import json_loads
+
+
+def os.chmod(*args):
+ try:
+ os.chmod(*args)
+ except Exception:
+ 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(string.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 = urllib.unquote(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 fs_join(*args):
+ """ joins a path, encoding aware """
+ return fs_encode(os.path.join(*[x if type(x) == unicode else decode(x) for x in args]))
+
+
+#: Deprecated method
+def save_join(*args):
+ return fs_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):
+ return safe_filename(encode(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
+ return False
+
+
+def formatSize(size):
+ """formats size of bytes"""
+ return bitmath.Byte(int(size)).best_prefix()
+
+
+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_frsize * 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_frsize
+
+
+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(htmlentitydefs.name2codepoint[name])
+ except KeyError:
+ pass
+
+ return text #: leave as is
+
+
+def has_method(obj, name):
+ """ Check if "name" was defined in obj, (false if it was inhereted) """
+ return hasattr(obj, '__dict__') and name in obj.__dict__
+
+
+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("."))))
+
+
+def load_translation(name, locale, default="en"):
+ """ Load language and return its translation object or None """
+
+ import traceback
+
+ try:
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation(name, os.path.join(pypath, "locale"),
+ languages=[locale, default], fallback=True)
+ except Exception:
+ traceback.print_exc()
+ return None
+ else:
+ translation.install(True)
+ return translation
diff --git a/pyload/utils/filters.py b/pyload/utils/filters.py
new file mode 100644
index 000000000..9d4d47c04
--- /dev/null
+++ b/pyload/utils/filters.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+import os
+
+quotechar = "::/"
+
+try:
+ from os.path import relpath
+except Exception:
+ from posixpath import curdir, sep, pardir
+
+
+ def os.relpath(path, start=curdir):
+ """Return a relative version of a path"""
+ if not path:
+ raise ValueError("no path specified")
+ start_list = os.path.abspath(start).split(sep)
+ path_list = os.path.abspath(path).split(sep)
+ # Work out how much of the filepath is shared by start and path.
+ i = len(os.path.commonprefix([start_list, path_list]))
+ rel_list = [pardir] * (len(start_list) - i) + path_list[i:]
+ if not rel_list:
+ return curdir
+ return os.path.join(*rel_list)
+
+
+def quotepath(path):
+ try:
+ return path.replace("../", quotechar)
+ except AttributeError:
+ return path
+ except Exception:
+ return ""
+
+
+def unquotepath(path):
+ try:
+ return path.replace(quotechar, "../")
+ except AttributeError:
+ return path
+ except Exception:
+ 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 = os.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/utils/middlewares.py b/pyload/utils/middlewares.py
new file mode 100644
index 000000000..c3f4952db
--- /dev/null
+++ b/pyload/utils/middlewares.py
@@ -0,0 +1,144 @@
+# -*- 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 Exception:
+ 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/utils/packagetools.py b/pyload/utils/packagetools.py
new file mode 100644
index 000000000..fed46123c
--- /dev/null
+++ b/pyload/utils/packagetools.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+import re
+import urlparse
+
+
+endings = ("jdeatme", "3gp", "7zip", "7z", "abr", "ac3", "aiff", "aifc", "aif", "ai",
+ "au", "avi", "apk", "bin", "bmp", "bat", "bz2", "cbr", "cbz", "ccf", "chm",
+ "cr2", "cso", "cue", "cvd", "dta", "deb", "divx", "djvu", "dlc", "dmg", "doc",
+ "docx", "dot", "eps", "epub", "exe", "ff", "flv", "flac", "f4v", "gsd", "gif",
+ "gpg", "gz", "iwd", "idx", "iso", "ipa", "ipsw", "java", "jar", "jpe?g", "load",
+ "m2ts", "m4v", "m4a", "md5", "mkv", "mp2", "mp3", "mp4", "mobi", "mov", "movie",
+ "mpeg", "mpe", "mpg", "mpq", "msi", "msu", "msp", "mv", "mws", "nfo", "npk", "oga",
+ "ogg", "ogv", "otrkey", "par2", "pkg", "png", "pdf", "pptx?", "ppsx?", "ppz", "pot",
+ "psd", "qt", "rmvb", "rm", "rar", "ram", "ra", "rev", "rnd", "rpm", "run", "rsdf",
+ "reg", "rtf", "shnf", "sh(?!tml)", "ssa", "smi", "sub", "srt", "snd", "sfv", "sfx",
+ "swf", "swc", "tar\.(gz|bz2|xz)", "tar", "tgz", "tiff?", "ts", "txt", "viv", "vivo",
+ "vob", "vtt", "webm", "wav", "wmv", "wma", "xla", "xls", "xpi", "zeno", "zip",
+ "[r-z]\d{2}", "_[_a-z]{2}", "\d{3,4}(?=\?|$|\"|\r|\n)")
+
+rarPats = [re.compile(r'(.*)(\.|_|-)pa?r?t?\.?\d+.(rar|exe)$', re.I),
+ re.compile(r'(.*)(\.|_|-)part\.?[0]*[1].(rar|exe)$', re.I),
+ re.compile(r'(.*)\.rar$', re.I),
+ re.compile(r'(.*)\.r\d+$', re.I),
+ re.compile(r'(.*)(\.|_|-)\d+$', re.I)]
+
+zipPats = [re.compile(r'(.*)\.zip$', re.I),
+ re.compile(r'(.*)\.z\d+$', re.I),
+ re.compile(r'(?is).*\.7z\.[\d]+$', re.I),
+ re.compile(r'(.*)\.a.$', re.I)]
+
+ffsjPats = [re.compile(r'(.*)\._((_[a-z])|([a-z]{2}))(\.|$)'),
+ re.compile(r'(.*)(\.|_|-)[\d]+(\.(' + '|'.join(endings) + ')$)', re.I)]
+
+iszPats = [re.compile(r'(.*)\.isz$', re.I),
+ re.compile(r'(.*)\.i\d{2}$', re.I)]
+
+pat0 = re.compile(r'www\d*\.', re.I)
+
+pat1 = re.compile(r'(\.?CD\d+)', re.I)
+pat2 = re.compile(r'(\.?part\d+)', re.I)
+
+pat3 = re.compile(r'(.+)[\.\-_]+$')
+pat4 = re.compile(r'(.+)\.\d+\.xtm$')
+
+
+def matchFirst(string, *args):
+ """ matches against list of regexp and returns first match """
+ for patternlist in args:
+ for pattern in patternlist:
+ m = pattern.search(string)
+ if m is not None:
+ name = m.group(1)
+ return name
+
+ return string
+
+
+def parseNames(files):
+ """ Generates packages names from name, data lists
+
+ :param files: list of (name, data)
+ :return: packagenames mapped to data lists (eg. urls)
+ """
+ packs = {}
+
+ 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 an 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
+ m = pat4.search(name)
+ if m is not None:
+ name = m.group(1)
+
+ # remove part and cd pattern
+ m = pat1.search(name)
+ if m is not None:
+ name = name.replace(m.group(0), "")
+ patternMatch = True
+
+ m = pat2.search(name)
+ if m is not None:
+ name = name.replace(m.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 . _ -
+ m = pat3.search(name)
+ if m is not None:
+ name = m.group(1)
+
+ # replace . and _ with space
+ name = name.replace(".", " ")
+ name = name.replace("_", " ")
+
+ name = name.strip()
+ else:
+ name = ""
+
+ #@NOTE: fallback: package by hoster
+ if not name:
+ name = urlparse.urlparse(file).netloc
+ if name:
+ name = pat0.sub("", name)
+
+ # fallback : default name
+ if not name:
+ name = _("Unnamed package")
+
+ # 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..e4f6a360a
--- /dev/null
+++ b/pyload/utils/printer.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# @author: vuolter
+
+import colorama
+
+colorama.init(autoreset=True)
+
+
+def color(color, text):
+ return colorama.Fore[c.upper()](text)
+
+for c in colorama.Fore:
+ eval("%(color)s = lambda msg: color(%(color)s, 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..76bb268ec
--- /dev/null
+++ b/pyload/utils/pylgettext.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+from gettext import *
+
+_searchdirs = None
+
+origfind = find
+
+
+def setpaths(pathlist):
+ global _searchdirs
+ _searchdirs = pathlist if isinstance(pathlist, list) else 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
+ _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 = []
+ 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..a0e849a11
--- /dev/null
+++ b/pyload/webui/__init__.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, vuolter
+
+import os
+import sys
+
+import beaker
+import bottle
+import jinja2
+
+import pyload.utils.pylgettext as gettext
+
+from pyload.Thread import Server
+from pyload.utils.middlewares import StripPathMiddleware, GZipMiddleWare, PrefixMiddleware
+from pyload.network.JsEngine import JsEngine
+from pyload.utils import decode, formatSize
+
+
+THEME_DIR = os.path.abspath(os.path.join(dirname(__file__), "themes"))
+PYLOAD_DIR = os.path.abspath(os.path.join(THEME_DIR, "..", "..", ".."))
+
+sys.path.append(PYLOAD_DIR)
+
+SETUP = None
+PYLOAD = None
+
+if not Server.core:
+ if Server.setup:
+ SETUP = Server.setup
+ config = SETUP.config
+ JS = JsEngine(SETUP)
+ else:
+ raise Exception("Could not access pyLoad Core")
+else:
+ PYLOAD = Server.core.api
+ config = Server.core.config
+ JS = JsEngine(Server.core)
+
+THEME = config.get('webui', 'theme')
+DL_ROOT = config.get('general', 'download_folder')
+LOG_ROOT = config.get('log', 'log_folder')
+PREFIX = config.get('webui', '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 = os.path.join("tmp", "jinja_cache")
+if not os.path.exists(cache):
+ os.makedirs(cache)
+
+bcc = jinja2.FileSystemBytecodeCache(cache, '%s.cache')
+
+loader = jinja2.FileSystemLoader([THEME_DIR, os.path.join(THEME_DIR, THEME)])
+
+env = jinja2.Environment(loader=loader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], trim_blocks=True, auto_reload=False,
+ bytecode_cache=bcc)
+
+from pyload.utils.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", os.path.join(PYLOAD_DIR, "locale"),
+ languages=[config.get("general", "language"), "en"],fallback=True)
+translation.install(True)
+env.install_gettext_translations(translation)
+
+session_opts = {
+ 'session.type': 'file',
+ 'session.cookie_expires': False,
+ 'session.data_dir': './tmp',
+ 'session.auto': False
+}
+
+web = StripPathMiddleware(beaker.middleware.SessionMiddleware(bottle.app(), session_opts))
+web = GZipMiddleWare(web)
+
+if PREFIX:
+ web = PrefixMiddleware(web, prefix=PREFIX)
+
+import pyload.webui.app
+
+
+def run_auto(host="0.0.0.0", port="8000"):
+ bottle.run(app=web, host=host, port=port, server="auto", quiet=True)
+
+
+def run_lightweight(host="0.0.0.0", port="8000"):
+ bottle.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
+
+ bottle.run(app=web, host=host, port=port, server=CherryPyWSGI, quiet=True)
+
+
+def run_fcgi(host="0.0.0.0", port="8000"):
+ bottle.run(app=web, host=host, port=port, server=bottle.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..99a7c2998
--- /dev/null
+++ b/pyload/webui/app/api.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+import itertools
+import traceback
+import urllib
+
+import SafeEval
+import bottle
+
+from pyload.Api import BaseObject
+from pyload.utils import json
+from pyload.webui import PYLOAD
+from pyload.webui.app.utils import toDict, set_session
+
+
+# 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
+@bottle.route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{},]*>')
+@bottle.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 bottle.HTTPError(403, json.dumps("Forbidden"))
+
+ if not PYLOAD.isAuthorized(func, {"role": s['role'], "permission": s['perms']}):
+ return bottle.HTTPError(401, json.dumps("Unauthorized"))
+
+ args = args.split("/")[1:]
+ kwargs = {}
+
+ for x, y in itertools.chain(request.GET.iteritems(), request.POST.iteritems()):
+ if x == "session":
+ continue
+ kwargs[x] = urllib.unquote(y)
+
+ try:
+ return callApi(func, *args, **kwargs)
+ except Exception, e:
+ traceback.print_exc()
+ return bottle.HTTPError(500, json.dumps({"error": e.message, "traceback": traceback.format_exc()}))
+
+
+def callApi(func, *args, **kwargs):
+ if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"):
+ print "Invalid API call", func
+ return bottle.HTTPError(404, json.dumps("Not Found"))
+
+ result = getattr(PYLOAD, func)(*[SafeEval.const_eval(x) for x in args],
+ **dict((x, SafeEval.const_eval(y)) for x, y in kwargs.iteritems()))
+
+ # null is invalid json response
+ return json.dumps(result or True, cls=TBaseEncoder)
+
+
+# post -> username, password
+@bottle.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 Exception:
+ return json.dumps(True)
+
+
+@bottle.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..465e087e5
--- /dev/null
+++ b/pyload/webui/app/cnl.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import base64
+import binascii
+import os
+import re
+import urllib
+
+import bottle
+
+from pyload.webui import PYLOAD, DL_ROOT, JS
+
+
+try:
+ from Crypto.Cipher import AES
+except Exception:
+ 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 bottle.HTTPError(403, "Forbidden")
+
+ return _view
+
+
+@bottle.route('/flash')
+@bottle.route('/flash/<id>')
+@bottle.route('/flash', method='POST')
+@local_check
+def flash(id="0"):
+ return "JDownloader\r\n"
+
+
+@bottle.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 ""
+
+
+@bottle.route('/flash/addcrypted', method='POST')
+@local_check
+def addcrypted():
+ package = request.forms.get('referer', 'ClickNLoad Package')
+ dlc = request.forms['crypted'].replace(" ", "+")
+
+ dlc_path = os.path.join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc")
+ with open(dlc_path, "wb") as dlc_file:
+ dlc_file.write(dlc)
+
+ try:
+ PYLOAD.addPackage(package, [dlc_path], 0)
+ except Exception:
+ return bottle.HTTPError()
+ else:
+ return "success\r\n"
+
+
+@bottle.route('/flash/addcrypted2', method='POST')
+@local_check
+def addcrypted2():
+ package = request.forms.get("source", None)
+ crypted = request.forms['crypted']
+ jk = request.forms['jk']
+
+ crypted = base64.standard_b64decode(urllib.unquote(crypted.replace(" ", "+")))
+ if JS:
+ jk = "%s f()" % jk
+ jk = JS.eval(jk)
+
+ else:
+ try:
+ jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1]
+ except Exception:
+ # 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 = binascii.unhexlify(jk)
+ except Exception:
+ 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 Exception:
+ return "failed can't add"
+ else:
+ return "success\r\n"
+
+
+@bottle.route('/flashgot_pyload')
+@bottle.route('/flashgot_pyload', method='POST')
+@bottle.route('/flashgot')
+@bottle.route('/flashgot', method='POST')
+@local_check
+def flashgot():
+ if request.environ['HTTP_REFERER'] not in ("http://localhost:9666/flashgot", "http://127.0.0.1:9666/flashgot"):
+ return bottle.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 ""
+
+
+@bottle.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
+
+
+@bottle.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()
+
+
+@bottle.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..a7fe8dcfb
--- /dev/null
+++ b/pyload/webui/app/json.py
@@ -0,0 +1,314 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import os
+import shutil
+import traceback
+
+import bottle
+
+from pyload.utils import decode, formatSize
+from pyload.webui import PYLOAD
+from pyload.webui.app.utils import login_required, render_to_response, toDict
+
+
+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']
+
+
+@bottle.route('/json/status')
+@bottle.route('/json/status', method='POST')
+@login_required('LIST')
+def status():
+ try:
+ status = toDict(PYLOAD.statusServer())
+ status['captcha'] = PYLOAD.isCaptchaWaiting()
+ return status
+ except Exception:
+ return bottle.HTTPError()
+
+
+@bottle.route('/json/links')
+@bottle.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:
+ traceback.print_exc()
+ return bottle.HTTPError()
+
+
+@bottle.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 Exception:
+ return bottle.HTTPError()
+
+
+@bottle.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 Exception:
+ traceback.print_exc()
+ return bottle.HTTPError()
+
+
+@bottle.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 Exception:
+ return bottle.HTTPError()
+
+
+@bottle.route('/json/abort_link/<id:int>')
+@login_required('DELETE')
+def abort_link(id):
+ try:
+ PYLOAD.stopDownloads([id])
+ return {"response": "success"}
+ except Exception:
+ return bottle.HTTPError()
+
+
+@bottle.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 Exception:
+ return bottle.HTTPError()
+
+
+@bottle.route('/json/add_package')
+@bottle.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 = os.path.join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename)
+ with open(fpath, 'wb') as destination:
+ shutil.copyfileobj(f.file, destination)
+ links.insert(0, fpath)
+ except Exception:
+ 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)
+
+
+@bottle.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 Exception:
+ return bottle.HTTPError()
+
+
+@bottle.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 Exception:
+ return bottle.HTTPError()
+
+
+@bottle.route('/json/set_captcha')
+@bottle.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 Exception:
+ 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}
+
+
+@bottle.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", {"sorted_conf": lambda c: sorted(c.items(), key=lambda i: i[1]['idx'] if i[0] not in ("desc", "outline") else 0),
+ "skey": section, "section": conf[section]})
+
+
+@bottle.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 Exception:
+ continue
+
+ if category == "general": category = "core"
+
+ PYLOAD.setConfigValue(section, option, decode(value), category)
+
+
+@bottle.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)
+
+
+@bottle.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)
+
+
+@bottle.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 bottle.HTTPError()
diff --git a/pyload/webui/app/pyload.py b/pyload/webui/app/pyload.py
new file mode 100644
index 000000000..58acdf12c
--- /dev/null
+++ b/pyload/webui/app/pyload.py
@@ -0,0 +1,526 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN
+
+import datetime
+import operator
+import os
+import sys
+import time
+import urllib
+
+import bottle
+
+from pyload.webui import PYLOAD, PYLOAD_DIR, THEME_DIR, THEME, SETUP, env
+
+from pyload.webui.app.utils import render_to_response, parse_permissions, parse_userdata, \
+ login_required, get_permission, set_permission, permlist, toDict, set_session
+
+from pyload.utils.filters import relpath, unquotepath
+
+from pyload.utils import formatSize, fs_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
+@bottle.error(403)
+def error403(code):
+ return "The parameter you passed has the wrong format"
+
+
+@bottle.error(404)
+def error404(code):
+ return "Sorry, this page does not exist"
+
+
+@bottle.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"])
+
+
+@bottle.route('/<theme>/<file:re:(.+/)?[^/]+\.min\.[^/]+>')
+def server_min(theme, file):
+ filename = os.path.join(THEME_DIR, THEME, theme, file)
+ if not os.path.isfile(filename):
+ file = file.replace(".min.", ".")
+ if file.endswith(".js"):
+ return server_js(theme, file)
+ else:
+ return server_static(theme, file)
+
+
+@bottle.route('/<theme>/<file:re:.+\.js>')
+def server_js(theme, file):
+ response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
+
+ if "/render/" in file or ".render." in file or True:
+ 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)
+
+
+@bottle.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 bottle.static_file(file, root=join(THEME_DIR, THEME, theme))
+
+
+@bottle.route('/favicon.ico')
+def favicon():
+ return bottle.static_file("icon.ico", root=join(PYLOAD_DIR, "docs", "resources"))
+
+
+@bottle.route('/login', method="GET")
+def login():
+ if not PYLOAD and SETUP:
+ bottle.redirect("/setup")
+ else:
+ return render_to_response("login.html", proc=[pre_processor])
+
+
+@bottle.route('/nopermission')
+def nopermission():
+ return base([_("You dont have permission to access this page.")])
+
+
+@bottle.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 bottle.redirect("/")
+
+
+@bottle.route('/logout')
+def logout():
+ s = request.environ.get('beaker.session')
+ s.delete()
+ return render_to_response("logout.html", proc=[pre_processor])
+
+
+@bottle.route('/')
+@bottle.route('/home')
+@login_required("LIST")
+def home():
+ try:
+ res = [toDict(x) for x in PYLOAD.statusDownloads()]
+ except Exception:
+ s = request.environ.get('beaker.session')
+ s.delete()
+ return bottle.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])
+
+
+@bottle.route('/queue')
+@login_required("LIST")
+def queue():
+ queue = PYLOAD.getQueue()
+
+ queue.sort(key=operator.attrgetter("order"))
+
+ return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor])
+
+
+@bottle.route('/collector')
+@login_required('LIST')
+def collector():
+ queue = PYLOAD.getCollector()
+
+ queue.sort(key=operator.attrgetter("order"))
+
+ return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor])
+
+
+@bottle.route('/downloads')
+@login_required('DOWNLOAD')
+def downloads():
+ root = PYLOAD.getConfigValue("general", "download_folder")
+
+ if not os.path.isdir(root):
+ return base([_('Download directory not found.')])
+ data = {
+ 'folder': [],
+ 'files': []
+ }
+
+ items = os.listdir(fs_encode(root))
+
+ for item in sorted([fs_decode(x) for x in items]):
+ if os.path.isdir(fs_join(root, item)):
+ folder = {
+ 'name': item,
+ 'path': item,
+ 'files': []
+ }
+ files = os.listdir(fs_join(root, item))
+ for file in sorted([fs_decode(x) for x in files]):
+ try:
+ if os.path.isfile(fs_join(root, item, file)):
+ folder['files'].append(file)
+ except Exception:
+ pass
+
+ data['folder'].append(folder)
+ elif os.path.isfile(os.path.join(root, item)):
+ data['files'].append(item)
+
+ return render_to_response('downloads.html', {'files': data}, [pre_processor])
+
+
+@bottle.route('/downloads/get/<path:path>')
+@login_required("DOWNLOAD")
+def get_download(path):
+ path = urllib.unquote(path).decode("utf8")
+ #@TODO some files can not be downloaded
+
+ root = PYLOAD.getConfigValue("general", "download_folder")
+
+ path = path.replace("..", "")
+ return bottle.static_file(fs_encode(path), fs_encode(root))
+
+
+@bottle.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))
+
+ last_name = None
+ for entry in sorted(plugin.keys()):
+ desc = plugin[entry].description
+ name, none, type = desc.partition("_")
+
+ if type in PYLOAD.core.pluginManager.TYPES:
+ if name == last_name or len([a for a, b in plugin.iteritems() if b.description.startswith(name + "_")]) > 1:
+ desc = name + " (" + type.title() + ")"
+ else:
+ desc = name
+ last_name = name
+ plugin_menu.append((entry, desc))
+
+ 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)
+
+ 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 Exception:
+ 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])
+
+
+@bottle.route('/filechooser')
+@bottle.route('/pathchooser')
+@bottle.route('/filechooser/<file:path>')
+@bottle.route('/pathchooser/<path:path>')
+@login_required('STATUS')
+def os.path(file="", path=""):
+ type = "file" if file else "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 = os.relpath(path)
+ else:
+ cwd = os.getcwd()
+
+ try:
+ cwd = cwd.encode("utf8")
+ except Exception:
+ pass
+
+ cwd = os.path.normpath(os.path.abspath(cwd))
+ parentdir = os.path.dirname(cwd)
+ if not abs:
+ if os.path.abspath(cwd) == "/":
+ cwd = os.relpath(cwd)
+ else:
+ cwd = os.relpath(cwd) + os.path.sep
+ parentdir = os.relpath(parentdir) + os.path.sep
+
+ if os.path.abspath(cwd) == "/":
+ parentdir = ""
+
+ try:
+ folders = os.listdir(cwd)
+ except Exception:
+ folders = []
+
+ files = []
+
+ for f in folders:
+ try:
+ f = f.decode(sys.getfilesystemencoding())
+ data = {'name': f, 'fullpath': os.path.join(cwd, f)}
+ data['sort'] = data['fullpath'].lower()
+ data['modified'] = datetime.datetime.fromtimestamp(int(os.path.getmtime(os.path.join(cwd, f))))
+ data['ext'] = os.path.splitext(f)[1]
+ except Exception:
+ continue
+
+ data['type'] = 'dir' if os.path.isdir(os.path.join(cwd, f)) else 'file'
+
+ if os.path.isfile(os.path.join(cwd, f)):
+ data['size'] = os.path.getsize(os.path.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=operator.itemgetter('type', 'sort'))
+
+ return render_to_response('pathchooser.html',
+ {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile,
+ 'absolute': abs}, [])
+
+
+@bottle.route('/logs')
+@bottle.route('/logs', method='POST')
+@bottle.route('/logs/<item>')
+@bottle.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")
+ color_template = PYLOAD.getConfigValue("log", "color_template") if PYLOAD.getConfigValue("log", "color_console") else ""
+ 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.datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S')
+ except Exception:
+ pass
+ try:
+ perpage = int(request.forms['perpage'])
+ s['perpage'] = perpage
+
+ reversed = bool(request.forms.get('reversed', False))
+ s['reversed'] = reversed
+ except Exception:
+ pass
+
+ s.save()
+
+ try:
+ item = int(item)
+ except Exception:
+ pass
+
+ log = PYLOAD.getLog()
+ if not perpage:
+ item = 1
+
+ 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.datetime: #: we will search for datetime.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.datetime.strptime(date + ' ' + time, '%Y-%m-%d %H:%M:%S')
+ except Exception:
+ dtime = None
+ date = '?'
+ time = ' '
+ level = '?'
+ message = l
+ if item == -1 and dtime is not None and fro <= dtime:
+ item = counter #: found our datetime.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.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,
+ 'color_template': color_template.title()},
+ [pre_processor])
+
+
+@bottle.route('/admin')
+@bottle.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'] = data['role'] is 0
+
+ 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])
+
+
+@bottle.route('/setup')
+def setup():
+ return base([_("Run pyload.py -s to access the setup.")])
+
+
+@bottle.route('/info')
+def info():
+ conf = PYLOAD.getConfigDict()
+ extra = os.uname() if hasattr(os, "uname") else tuple()
+
+ data = {"python" : sys.version,
+ "os" : " ".join((os.name, sys.platform) + extra),
+ "version" : PYLOAD.getServerVersion(),
+ "folder" : os.path.abspath(PYLOAD_DIR), "config": os.path.abspath(""),
+ "download" : os.path.abspath(conf['general']['download_folder']['value']),
+ "freespace": formatSize(PYLOAD.freeSpace()),
+ "remote" : conf['remote']['port']['value'],
+ "webif" : conf['webui']['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..2e7cf76c5
--- /dev/null
+++ b/pyload/webui/app/utils.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+# @author: RaNaN, vuolter
+
+import os
+
+import bottle
+
+from pyload.Api import has_permission, PERMS, ROLE
+from pyload.webui import env, THEME
+
+
+def render_to_response(file, args={}, proc=[]):
+ for p in proc:
+ args.update(p())
+ path = "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" : session.get("role", 1) == 0,
+ "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 bottle.HTTPError(403, "Forbidden")
+ else:
+ return bottle.redirect("/nopermission")
+
+ return func(*args, **kwargs)
+ else:
+ if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
+ return bottle.HTTPError(403, "Forbidden")
+ else:
+ return bottle.redirect("/login")
+
+ return _view
+
+ return _dec
+
+
+def toDict(obj):
+ return {att: getattr(obj, att) for att in obj.__slots__}
+
+
+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/servers/lighttpd_default.conf b/pyload/webui/servers/lighttpd_default.conf
new file mode 100644
index 000000000..9ccd264db
--- /dev/null
+++ b/pyload/webui/servers/lighttpd_default.conf
@@ -0,0 +1,154 @@
+# 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)
diff --git a/pyload/webui/servers/nginx_default.conf b/pyload/webui/servers/nginx_default.conf
new file mode 100644
index 000000000..c1cfcafca
--- /dev/null
+++ b/pyload/webui/servers/nginx_default.conf
@@ -0,0 +1,87 @@
+daemon off;
+pid %(path)/nginx.pid;
+worker_processes 2;
+
+error_log %(path)/error.log info;
+
+events {
+ worker_connections 1024;
+ use epoll;
+}
+
+http {
+ include /etc/nginx/conf/mime.types;
+ default_type application/octet-stream;
+
+ %(ssl)
+
+ log_format main
+ '$remote_addr - $remote_user [$time_local] '
+ '"$request" $status $bytes_sent '
+ '"$http_referer" "$http_user_agent" '
+ '"$gzip_ratio"';
+
+ error_log %(path)/error.log info;
+
+ client_header_timeout 10m;
+ client_body_timeout 10m;
+ send_timeout 10m;
+
+ client_body_temp_path %(path)/client_body_temp;
+ proxy_temp_path %(path)/proxy_temp;
+ fastcgi_temp_path %(path)/fastcgi_temp;
+
+
+ connection_pool_size 256;
+ client_header_buffer_size 1k;
+ large_client_header_buffers 4 2k;
+ request_pool_size 4k;
+
+ gzip on;
+ gzip_min_length 1100;
+ gzip_buffers 4 8k;
+ gzip_types text/plain;
+
+ output_buffers 1 32k;
+ postpone_output 1460;
+
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+
+ keepalive_timeout 75 20;
+
+ ignore_invalid_headers on;
+
+ server {
+ listen %(port);
+ server_name %(host);
+ # site_media - folder in uri for static files
+ location ^~ /media {
+ root %(media)/..;
+ }
+ location ^~ /admin/media {
+ root /usr/lib/python%(version)/site-packages/django/contrib;
+ }
+location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
+ access_log off;
+ expires 30d;
+}
+ location / {
+ # host and port to fastcgi server
+ fastcgi_pass 127.0.0.1:9295;
+ fastcgi_param PATH_INFO $fastcgi_script_name;
+ fastcgi_param REQUEST_METHOD $request_method;
+ fastcgi_param QUERY_STRING $query_string;
+ fastcgi_param CONTENT_TYPE $content_type;
+ fastcgi_param CONTENT_LENGTH $content_length;
+ fastcgi_param SERVER_NAME $server_name;
+ fastcgi_param SERVER_PORT $server_port;
+ fastcgi_param SERVER_PROTOCOL $server_protocol;
+ fastcgi_pass_header Authorization;
+ fastcgi_intercept_errors off;
+ }
+ access_log %(path)/access.log main;
+ error_log %(path)/error.log;
+ }
+ }
diff --git a/pyload/webui/themes/Dark/css/MooDialog.css b/pyload/webui/themes/Dark/css/MooDialog.css
new file mode 100644
index 000000000..3c070efa9
--- /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(../lib/MooTools/MooDialog/css/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(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+ color:white;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Dark/css/base.css b/pyload/webui/themes/Dark/css/base.css
new file mode 100644
index 000000000..80dc363e1
--- /dev/null
+++ b/pyload/webui/themes/Dark/css/base.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/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: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/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;
+}
+
+#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..3362e9381
--- /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/pyload/webui/themes/Dark/css/pathchooser.css b/pyload/webui/themes/Dark/css/pathchooser.css
new file mode 100644
index 000000000..204812aa1
--- /dev/null
+++ b/pyload/webui/themes/Dark/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/Dark/css/window.css b/pyload/webui/themes/Dark/css/window.css
new file mode 100644
index 000000000..1bcc58e6c
--- /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/default/img/add_folder.png b/pyload/webui/themes/Dark/img/add_folder.png
index 8acbc411b..8acbc411b 100644
--- a/module/web/media/default/img/add_folder.png
+++ b/pyload/webui/themes/Dark/img/add_folder.png
Binary files differ
diff --git a/module/web/media/default/img/ajax-loader.gif b/pyload/webui/themes/Dark/img/ajax-loader.gif
index 2fd8e0737..2fd8e0737 100644
--- a/module/web/media/default/img/ajax-loader.gif
+++ b/pyload/webui/themes/Dark/img/ajax-loader.gif
Binary files differ
diff --git a/module/web/media/default/img/arrow_refresh.png b/pyload/webui/themes/Dark/img/arrow_refresh.png
index 0de26566d..0de26566d 100644
--- a/module/web/media/default/img/arrow_refresh.png
+++ b/pyload/webui/themes/Dark/img/arrow_refresh.png
Binary files differ
diff --git a/module/web/media/default/img/arrow_right.png b/pyload/webui/themes/Dark/img/arrow_right.png
index b1a181923..b1a181923 100644
--- a/module/web/media/default/img/arrow_right.png
+++ b/pyload/webui/themes/Dark/img/arrow_right.png
Binary files differ
diff --git a/module/web/media/default/img/big_button.gif b/pyload/webui/themes/Dark/img/big_button.gif
index 7680490ea..7680490ea 100644
--- a/module/web/media/default/img/big_button.gif
+++ b/pyload/webui/themes/Dark/img/big_button.gif
Binary files differ
diff --git a/module/web/media/default/img/big_button_over.gif b/pyload/webui/themes/Dark/img/big_button_over.gif
index 2e3ee10d2..2e3ee10d2 100644
--- a/module/web/media/default/img/big_button_over.gif
+++ b/pyload/webui/themes/Dark/img/big_button_over.gif
Binary files differ
diff --git a/module/web/media/default/img/body.png b/pyload/webui/themes/Dark/img/body.png
index 7ff1043e0..7ff1043e0 100644
--- a/module/web/media/default/img/body.png
+++ b/pyload/webui/themes/Dark/img/body.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/module/web/media/default/img/closebtn.gif b/pyload/webui/themes/Dark/img/closebtn.gif
index 3e27e6030..3e27e6030 100644
--- a/module/web/media/default/img/closebtn.gif
+++ b/pyload/webui/themes/Dark/img/closebtn.gif
Binary files differ
diff --git a/module/web/media/default/img/cog.png b/pyload/webui/themes/Dark/img/cog.png
index 67de2c6cc..67de2c6cc 100644
--- a/module/web/media/default/img/cog.png
+++ b/pyload/webui/themes/Dark/img/cog.png
Binary files differ
diff --git a/module/web/media/default/img/control_add.png b/pyload/webui/themes/Dark/img/control_add.png
index d39886893..d39886893 100644
--- a/module/web/media/default/img/control_add.png
+++ b/pyload/webui/themes/Dark/img/control_add.png
Binary files differ
diff --git a/module/web/media/default/img/control_add_blue.png b/pyload/webui/themes/Dark/img/control_add_blue.png
index d11b7f41d..d11b7f41d 100644
--- a/module/web/media/default/img/control_add_blue.png
+++ b/pyload/webui/themes/Dark/img/control_add_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_cancel.png b/pyload/webui/themes/Dark/img/control_cancel.png
index 7b9bc3fba..7b9bc3fba 100644
--- a/module/web/media/default/img/control_cancel.png
+++ b/pyload/webui/themes/Dark/img/control_cancel.png
Binary files differ
diff --git a/module/web/media/default/img/control_cancel_blue.png b/pyload/webui/themes/Dark/img/control_cancel_blue.png
index 0c5c96ce3..0c5c96ce3 100644
--- a/module/web/media/default/img/control_cancel_blue.png
+++ b/pyload/webui/themes/Dark/img/control_cancel_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_pause.png b/pyload/webui/themes/Dark/img/control_pause.png
index 2d9ce9c4e..2d9ce9c4e 100644
--- a/module/web/media/default/img/control_pause.png
+++ b/pyload/webui/themes/Dark/img/control_pause.png
Binary files differ
diff --git a/module/web/media/default/img/control_pause_blue.png b/pyload/webui/themes/Dark/img/control_pause_blue.png
index ec61099b0..ec61099b0 100644
--- a/module/web/media/default/img/control_pause_blue.png
+++ b/pyload/webui/themes/Dark/img/control_pause_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_play.png b/pyload/webui/themes/Dark/img/control_play.png
index 0846555d0..0846555d0 100644
--- a/module/web/media/default/img/control_play.png
+++ b/pyload/webui/themes/Dark/img/control_play.png
Binary files differ
diff --git a/module/web/media/default/img/control_play_blue.png b/pyload/webui/themes/Dark/img/control_play_blue.png
index f8c8ec683..f8c8ec683 100644
--- a/module/web/media/default/img/control_play_blue.png
+++ b/pyload/webui/themes/Dark/img/control_play_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_stop.png b/pyload/webui/themes/Dark/img/control_stop.png
index 893bb60e5..893bb60e5 100644
--- a/module/web/media/default/img/control_stop.png
+++ b/pyload/webui/themes/Dark/img/control_stop.png
Binary files differ
diff --git a/module/web/media/default/img/control_stop_blue.png b/pyload/webui/themes/Dark/img/control_stop_blue.png
index e6f75d232..e6f75d232 100644
--- a/module/web/media/default/img/control_stop_blue.png
+++ b/pyload/webui/themes/Dark/img/control_stop_blue.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/delete.png b/pyload/webui/themes/Dark/img/delete.png
index 08f249365..08f249365 100644
--- a/module/web/media/default/img/delete.png
+++ b/pyload/webui/themes/Dark/img/delete.png
Binary files differ
diff --git a/module/web/media/default/img/drag_corner.gif b/pyload/webui/themes/Dark/img/drag_corner.gif
index befb1adf1..befb1adf1 100644
--- a/module/web/media/default/img/drag_corner.gif
+++ b/pyload/webui/themes/Dark/img/drag_corner.gif
Binary files differ
diff --git a/module/web/media/default/img/error.png b/pyload/webui/themes/Dark/img/error.png
index c37bd062e..c37bd062e 100644
--- a/module/web/media/default/img/error.png
+++ b/pyload/webui/themes/Dark/img/error.png
Binary files differ
diff --git a/module/web/media/default/img/folder.png b/pyload/webui/themes/Dark/img/folder.png
index 784e8fa48..784e8fa48 100644
--- a/module/web/media/default/img/folder.png
+++ b/pyload/webui/themes/Dark/img/folder.png
Binary files differ
diff --git a/module/web/media/default/img/full.png b/pyload/webui/themes/Dark/img/full.png
index fea52af76..fea52af76 100644
--- a/module/web/media/default/img/full.png
+++ b/pyload/webui/themes/Dark/img/full.png
Binary files differ
diff --git a/module/web/media/default/img/head-login.png b/pyload/webui/themes/Dark/img/head-login.png
index b59b7cbbf..b59b7cbbf 100644
--- a/module/web/media/default/img/head-login.png
+++ b/pyload/webui/themes/Dark/img/head-login.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-collector.png b/pyload/webui/themes/Dark/img/head-menu-collector.png
index 861be40bc..861be40bc 100644
--- a/module/web/media/default/img/head-menu-collector.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-config.png
index bbf43d4f3..bbf43d4f3 100644
--- a/module/web/media/default/img/head-menu-config.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-development.png
index fad150fe1..fad150fe1 100644
--- a/module/web/media/default/img/head-menu-development.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-download.png
index 98c5da9db..98c5da9db 100644
--- a/module/web/media/default/img/head-menu-download.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-home.png
index 9d62109aa..9d62109aa 100644
--- a/module/web/media/default/img/head-menu-home.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-index.png
index 44d631064..44d631064 100644
--- a/module/web/media/default/img/head-menu-index.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-news.png
index 43950ebc9..43950ebc9 100644
--- a/module/web/media/default/img/head-menu-news.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-queue.png
index be98793ce..be98793ce 100644
--- a/module/web/media/default/img/head-menu-queue.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-recent.png
index fc9b0497f..fc9b0497f 100644
--- a/module/web/media/default/img/head-menu-recent.png
+++ b/pyload/webui/themes/Dark/img/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/head-menu-wiki.png
index 07cf0102d..07cf0102d 100644
--- a/module/web/media/default/img/head-menu-wiki.png
+++ b/pyload/webui/themes/Dark/img/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/head-search-noshadow.png
index aafdae015..aafdae015 100644
--- a/module/web/media/default/img/head-search-noshadow.png
+++ b/pyload/webui/themes/Dark/img/head-search-noshadow.png
Binary files differ
diff --git a/module/web/media/default/img/head_bg1.png b/pyload/webui/themes/Dark/img/head_bg1.png
index f2848c3cc..f2848c3cc 100644
--- a/module/web/media/default/img/head_bg1.png
+++ b/pyload/webui/themes/Dark/img/head_bg1.png
Binary files differ
diff --git a/module/web/media/default/img/images.png b/pyload/webui/themes/Dark/img/images.png
index 184860d1e..184860d1e 100644
--- a/module/web/media/default/img/images.png
+++ b/pyload/webui/themes/Dark/img/images.png
Binary files differ
diff --git a/module/web/media/default/img/notice.png b/pyload/webui/themes/Dark/img/notice.png
index 12cd1aef9..12cd1aef9 100644
--- a/module/web/media/default/img/notice.png
+++ b/pyload/webui/themes/Dark/img/notice.png
Binary files differ
diff --git a/module/web/media/default/img/package_go.png b/pyload/webui/themes/Dark/img/package_go.png
index aace63ad6..aace63ad6 100644
--- a/module/web/media/default/img/package_go.png
+++ b/pyload/webui/themes/Dark/img/package_go.png
Binary files differ
diff --git a/module/web/media/default/img/page-tools-backlinks.png b/pyload/webui/themes/Dark/img/page-tools-backlinks.png
index 3eb6a9ce3..3eb6a9ce3 100644
--- a/module/web/media/default/img/page-tools-backlinks.png
+++ b/pyload/webui/themes/Dark/img/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/page-tools-edit.png
index 188e1c12b..188e1c12b 100644
--- a/module/web/media/default/img/page-tools-edit.png
+++ b/pyload/webui/themes/Dark/img/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/page-tools-revisions.png
index 5c3b8587f..5c3b8587f 100644
--- a/module/web/media/default/img/page-tools-revisions.png
+++ b/pyload/webui/themes/Dark/img/page-tools-revisions.png
Binary files differ
diff --git a/module/web/media/default/img/parseUri.png b/pyload/webui/themes/Dark/img/parseUri.png
index 937bded9d..937bded9d 100644
--- a/module/web/media/default/img/parseUri.png
+++ b/pyload/webui/themes/Dark/img/parseUri.png
Binary files differ
diff --git a/module/web/media/default/img/pencil.png b/pyload/webui/themes/Dark/img/pencil.png
index 0bfecd50e..0bfecd50e 100644
--- a/module/web/media/default/img/pencil.png
+++ b/pyload/webui/themes/Dark/img/pencil.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/module/web/media/default/img/reconnect.png b/pyload/webui/themes/Dark/img/reconnect.png
index 49b269145..49b269145 100644
--- a/module/web/media/default/img/reconnect.png
+++ b/pyload/webui/themes/Dark/img/reconnect.png
Binary files differ
diff --git a/module/web/media/default/img/status_None.png b/pyload/webui/themes/Dark/img/status_None.png
index 293b13f77..293b13f77 100644
--- a/module/web/media/default/img/status_None.png
+++ b/pyload/webui/themes/Dark/img/status_None.png
Binary files differ
diff --git a/module/web/media/default/img/status_downloading.png b/pyload/webui/themes/Dark/img/status_downloading.png
index fb4ebc850..fb4ebc850 100644
--- a/module/web/media/default/img/status_downloading.png
+++ b/pyload/webui/themes/Dark/img/status_downloading.png
Binary files differ
diff --git a/module/web/media/default/img/status_failed.png b/pyload/webui/themes/Dark/img/status_failed.png
index c37bd062e..c37bd062e 100644
--- a/module/web/media/default/img/status_failed.png
+++ b/pyload/webui/themes/Dark/img/status_failed.png
Binary files differ
diff --git a/module/web/media/default/img/status_finished.png b/pyload/webui/themes/Dark/img/status_finished.png
index 89c8129a4..89c8129a4 100644
--- a/module/web/media/default/img/status_finished.png
+++ b/pyload/webui/themes/Dark/img/status_finished.png
Binary files differ
diff --git a/module/web/media/default/img/status_offline.png b/pyload/webui/themes/Dark/img/status_offline.png
index 0cfd58596..0cfd58596 100644
--- a/module/web/media/default/img/status_offline.png
+++ b/pyload/webui/themes/Dark/img/status_offline.png
Binary files differ
diff --git a/module/web/media/default/img/status_proc.png b/pyload/webui/themes/Dark/img/status_proc.png
index 67de2c6cc..67de2c6cc 100644
--- a/module/web/media/default/img/status_proc.png
+++ b/pyload/webui/themes/Dark/img/status_proc.png
Binary files differ
diff --git a/module/web/media/default/img/status_queue.png b/pyload/webui/themes/Dark/img/status_queue.png
index 293b13f77..293b13f77 100644
--- a/module/web/media/default/img/status_queue.png
+++ b/pyload/webui/themes/Dark/img/status_queue.png
Binary files differ
diff --git a/module/web/media/default/img/status_waiting.png b/pyload/webui/themes/Dark/img/status_waiting.png
index 2842cc338..2842cc338 100644
--- a/module/web/media/default/img/status_waiting.png
+++ b/pyload/webui/themes/Dark/img/status_waiting.png
Binary files differ
diff --git a/module/web/media/default/img/success.png b/pyload/webui/themes/Dark/img/success.png
index 89c8129a4..89c8129a4 100644
--- a/module/web/media/default/img/success.png
+++ b/pyload/webui/themes/Dark/img/success.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/module/web/media/default/img/tabs-border-bottom.png b/pyload/webui/themes/Dark/img/tabs-border-bottom.png
index 02440f428..02440f428 100644
--- a/module/web/media/default/img/tabs-border-bottom.png
+++ b/pyload/webui/themes/Dark/img/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/user-actions-logout.png
index 0010931e2..0010931e2 100644
--- a/module/web/media/default/img/user-actions-logout.png
+++ b/pyload/webui/themes/Dark/img/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/user-actions-profile.png
index 46573fff6..46573fff6 100644
--- a/module/web/media/default/img/user-actions-profile.png
+++ b/pyload/webui/themes/Dark/img/user-actions-profile.png
Binary files differ
diff --git a/module/web/media/default/img/user-info.png b/pyload/webui/themes/Dark/img/user-info.png
index 6e643100f..6e643100f 100644
--- a/module/web/media/default/img/user-info.png
+++ b/pyload/webui/themes/Dark/img/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/js/admin.coffee b/pyload/webui/themes/Dark/js/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/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/admin.min.js b/pyload/webui/themes/Dark/js/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/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/base.coffee b/pyload/webui/themes/Dark/js/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/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/base.min.js b/pyload/webui/themes/Dark/js/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/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/package.js b/pyload/webui/themes/Dark/js/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/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/settings.coffee b/pyload/webui/themes/Dark/js/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/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/settings.min.js b/pyload/webui/themes/Dark/js/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/Dark/js/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/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+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('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: 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/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+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: [Core/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));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ 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');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/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/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+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: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ 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,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ 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,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ 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;
+ }
+
+});
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ 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 .content {
+ height: 100px;
+}
+
+.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 {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/module/web/media/img/dialog-close.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png
index 81ebb88b2..81ebb88b2 100644
--- a/module/web/media/img/dialog-close.png
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/module/web/media/img/dialog-question.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png
index b0af3db5b..b0af3db5b 100644
--- a/module/web/media/img/dialog-question.png
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/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/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ 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;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+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]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// 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: 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: 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: 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: 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';
+ }
+
+ 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.name == '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;
+ window = this.Window = document = 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: 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: 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: 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, currentExpression;
+ 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ 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];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</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 classes(this.className).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('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+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){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ 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: 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 normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+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 == 'wheel' || 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 == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ 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: 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
+ wheel: 2, 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, message: 2 //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--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) 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.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,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+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();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ 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: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+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.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 heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+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();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ 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.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: 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: 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: '',
+ withCredentials: false,*/
+ 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;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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.withCredentials) && '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();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].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) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+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/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+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.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+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;
+
+};
+
+/*
+---
+
+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);
+});
+
+})();
+
+/*
+---
+
+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,
+ compensateScroll: 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.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ 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),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ 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(offsetParent);
+ 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 = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ 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];
+ }
+ },
+
+ 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);
+ this.mouse.start = null;
+ 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 = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['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 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ 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: 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: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+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;
+ }
+
+});
+
+/*
+---
+
+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));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+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: 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: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+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('&');
+ }
+
+});
+
+/*
+---
+
+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: 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: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+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: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+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;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(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 ('ActiveXObject' in window){
+ 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);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/Purr/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/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Dark/lib/MooTools/TinyTab/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/tml/admin.html b/pyload/webui/themes/Dark/tml/admin.html
new file mode 100644
index 000000000..c5cdb494b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/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..cc2c93db9
--- /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="/css/base.css"/>
+<link rel="stylesheet" type="text/css" href="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/>
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/js/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="/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="/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="/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="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/img/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="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2015 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 '/tml/window.html' %}
+ {% include '/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..3bfa6cbcf
--- /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>
diff --git a/pyload/webui/themes/Dark/tml/downloads.html b/pyload/webui/themes/Dark/tml/downloads.html
new file mode 100644
index 000000000..f6e581c5b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/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/Dark/tml/folder.html b/pyload/webui/themes/Dark/tml/folder.html
new file mode 100644
index 000000000..21f3f4a03
--- /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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/Dark/tml/home.html b/pyload/webui/themes/Dark/tml/home.html
new file mode 100644
index 000000000..6ce60de0b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/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': '/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="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/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="/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/Dark/tml/info.html b/pyload/webui/themes/Dark/tml/info.html
new file mode 100644
index 000000000..62a8df571
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/info.html
@@ -0,0 +1,76 @@
+{% extends '/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 %}
diff --git a/pyload/webui/themes/Dark/tml/login.html b/pyload/webui/themes/Dark/tml/login.html
new file mode 100644
index 000000000..004bc506b
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/login.html
@@ -0,0 +1,37 @@
+{% extends '/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..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/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/Dark/tml/logs.html b/pyload/webui/themes/Dark/tml/logs.html
new file mode 100644
index 000000000..429306aae
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/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 %}
diff --git a/pyload/webui/themes/Dark/tml/pathchooser.html b/pyload/webui/themes/Dark/tml/pathchooser.html
new file mode 100644
index 000000000..6e58ab536
--- /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="/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>
diff --git a/pyload/webui/themes/Dark/tml/queue.html b/pyload/webui/themes/Dark/tml/queue.html
new file mode 100644
index 000000000..6e7cb6754
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.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="/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="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/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 %}
diff --git a/pyload/webui/themes/Dark/tml/settings.html b/pyload/webui/themes/Dark/tml/settings.html
new file mode 100644
index 000000000..7df0b2f23
--- /dev/null
+++ b/pyload/webui/themes/Dark/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/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 %}
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..c7e60865e
--- /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 sorted_conf(section) %}
+ {% 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>
diff --git a/pyload/webui/themes/Dark/tml/window.html b/pyload/webui/themes/Dark/tml/window.html
new file mode 100644
index 000000000..8f5efae36
--- /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="/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">
+<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>
diff --git a/pyload/webui/themes/Default/css/base.css b/pyload/webui/themes/Default/css/base.css
new file mode 100644
index 000000000..f5078bfbb
--- /dev/null
+++ b/pyload/webui/themes/Default/css/base.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..33e67397d
--- /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..204812aa1
--- /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/pyload/webui/themes/Default/css/window.css b/pyload/webui/themes/Default/css/window.css
new file mode 100644
index 000000000..b469b4259
--- /dev/null
+++ b/pyload/webui/themes/Default/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/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/admin.coffee b/pyload/webui/themes/Default/js/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/Default/js/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/admin.min.js b/pyload/webui/themes/Default/js/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/Default/js/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/base.coffee b/pyload/webui/themes/Default/js/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/Default/js/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/base.min.js b/pyload/webui/themes/Default/js/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/Default/js/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/package.js b/pyload/webui/themes/Default/js/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/Default/js/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/settings.coffee b/pyload/webui/themes/Default/js/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/Default/js/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/settings.min.js b/pyload/webui/themes/Default/js/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/Default/js/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/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+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('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: 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/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+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: [Core/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));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ 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');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/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/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+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: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ 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,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ 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,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ 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;
+ }
+
+});
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ 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 .content {
+ height: 100px;
+}
+
+.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 {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/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/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ 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;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+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]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// 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: 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: 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: 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: 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';
+ }
+
+ 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.name == '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;
+ window = this.Window = document = 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: 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: 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: 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, currentExpression;
+ 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ 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];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</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 classes(this.className).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('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+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){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ 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: 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 normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+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 == 'wheel' || 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 == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ 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: 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
+ wheel: 2, 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, message: 2 //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--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) 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.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,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+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();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ 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: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+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.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 heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+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();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ 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.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: 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: 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: '',
+ withCredentials: false,*/
+ 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;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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.withCredentials) && '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();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].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) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+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/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+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.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+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;
+
+};
+
+/*
+---
+
+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);
+});
+
+})();
+
+/*
+---
+
+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,
+ compensateScroll: 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.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ 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),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ 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(offsetParent);
+ 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 = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ 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];
+ }
+ },
+
+ 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);
+ this.mouse.start = null;
+ 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 = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['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 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ 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: 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: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+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;
+ }
+
+});
+
+/*
+---
+
+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));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+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: 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: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+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('&');
+ }
+
+});
+
+/*
+---
+
+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: 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: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+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: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+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;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(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 ('ActiveXObject' in window){
+ 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);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/Purr/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/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Default/lib/MooTools/TinyTab/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/tml/admin.html b/pyload/webui/themes/Default/tml/admin.html
new file mode 100644
index 000000000..c5cdb494b
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/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..116fb9b51
--- /dev/null
+++ b/pyload/webui/themes/Default/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="/css/base.css"/>
+<link rel="stylesheet" type="text/css" href="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/lib/MooTools/MooDialog/css/MooDialog.css"/>
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/js/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="/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="/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="/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="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/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="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2015 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 '/tml/window.html' %}
+ {% include '/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..3bfa6cbcf
--- /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..f6e581c5b
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/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..102d53e7c
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/filemanager.html
@@ -0,0 +1,76 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+
+<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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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..21f3f4a03
--- /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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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..6ce60de0b
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/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': '/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="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/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="/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..e62a2d6e8
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/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..74bbb70a4
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/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..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/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..429306aae
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/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 %}
diff --git a/pyload/webui/themes/Default/tml/pathchooser.html b/pyload/webui/themes/Default/tml/pathchooser.html
new file mode 100644
index 000000000..6e58ab536
--- /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="/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>
diff --git a/pyload/webui/themes/Default/tml/queue.html b/pyload/webui/themes/Default/tml/queue.html
new file mode 100644
index 000000000..0affa54e0
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.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="/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="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/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..e96dcf22a
--- /dev/null
+++ b/pyload/webui/themes/Default/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/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..83a008619
--- /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 sorted_conf(section) %}
+ {% 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..023441f69
--- /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="/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..e7fb7c23b
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/MooDialog.css
@@ -0,0 +1,85 @@
+/* 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-color: #E44424;
+ color: black;
+ opacity:0.9;
+}
+
+.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(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Flat/css/base.css b/pyload/webui/themes/Flat/css/base.css
new file mode 100644
index 000000000..fd73d0a19
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/base.css
@@ -0,0 +1,866 @@
+.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;
+ 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:#67BCDB;
+ color:black;
+ display:inline;
+ list-style-type:none;
+ margin:0;
+ padding:10px;
+}
+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:#67BCDB;
+ 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:#FFFFFF;
+ background-position:0 100%;
+ background-repeat:repeat no-repeat;
+ border-color:#CCCCCC #CCCCCC transparent;
+ color:#555555;
+ padding:7px 15px 8px;
+ text-decoration:none;
+ opacity:0.7;
+}
+#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;
+ outline:none;
+ padding-bottom:7px;
+ opacity:1;
+ background-color: #FF9009;
+ transition: background 0.2s ease-out, opacity 0.5s ease-in;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ border-bottom-color:transparent;
+ color:#3566A5;
+ padding:7px 15px 8px;
+ opacity:1;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#111111;
+}
+div#head-search-and-login {
+ float:right;
+ margin:0 12px 0 0;
+ padding:7px 0px 7px 7px;
+ white-space:nowrap;
+ background-color:#FFF056;
+ opacity:0.9;
+}
+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..c2f21b506
--- /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..204812aa1
--- /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..40c1aadca
--- /dev/null
+++ b/pyload/webui/themes/Flat/css/window.css
@@ -0,0 +1,70 @@
+/* ----------- stylized ----------- */
+.window_box h1{
+ margin-bottom:8px;
+}
+.window_box p{
+ font-size:11px;
+ color:#FFFFFF;
+ 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:#FFFFFF;
+ 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;
+ 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;
+ color:#FFFFFF;
+}
+.window_box .cont input{
+ float: none;
+ margin: 0px 15px 0px 1px;
+}
+.window_box textarea{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ width:300px;
+ margin:2px 0 20px 10px;
+}
+.window_box button, .styled_button{
+ clear:both;
+ margin-left:150px;
+ width:125px;
+ height:31px;
+ background: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/add_folder.png b/pyload/webui/themes/Flat/img/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/ajax-loader.gif b/pyload/webui/themes/Flat/img/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/ajax-loader.gif
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/big_button.gif b/pyload/webui/themes/Flat/img/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/big_button_over.gif b/pyload/webui/themes/Flat/img/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/body.png b/pyload/webui/themes/Flat/img/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/body.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/closebtn.gif b/pyload/webui/themes/Flat/img/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/closebtn.gif
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/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/drag_corner.gif b/pyload/webui/themes/Flat/img/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/drag_corner.gif
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/full.png b/pyload/webui/themes/Flat/img/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/full.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-recent.png b/pyload/webui/themes/Flat/img/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head-menu-recent.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/head_bg1.png b/pyload/webui/themes/Flat/img/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/images.png b/pyload/webui/themes/Flat/img/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/images.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/parseUri.png b/pyload/webui/themes/Flat/img/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/parseUri.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/pyload-logo.png b/pyload/webui/themes/Flat/img/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/pyload-logo.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/tab-background.png b/pyload/webui/themes/Flat/img/tab-background.png
new file mode 100644
index 000000000..29a5d1991
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/img/tabs-border-bottom.png b/pyload/webui/themes/Flat/img/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/Flat/img/tabs-border-bottom.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/admin.coffee b/pyload/webui/themes/Flat/js/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/admin.min.js b/pyload/webui/themes/Flat/js/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/base.coffee b/pyload/webui/themes/Flat/js/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/base.min.js b/pyload/webui/themes/Flat/js/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/filemanager.js b/pyload/webui/themes/Flat/js/filemanager.js
new file mode 100644
index 000000000..ab9b8b492
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/Flat/js/package.js b/pyload/webui/themes/Flat/js/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/settings.coffee b/pyload/webui/themes/Flat/js/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/settings.min.js b/pyload/webui/themes/Flat/js/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/Flat/js/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/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+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('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: 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/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+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: [Core/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));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ 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');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/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/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+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: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ 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,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ 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,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ 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;
+ }
+
+});
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ 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 .content {
+ height: 100px;
+}
+
+.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 {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/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/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ 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;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+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]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// 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: 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: 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: 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: 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';
+ }
+
+ 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.name == '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;
+ window = this.Window = document = 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: 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: 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: 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, currentExpression;
+ 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ 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];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</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 classes(this.className).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('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+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){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ 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: 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 normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+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 == 'wheel' || 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 == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ 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: 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
+ wheel: 2, 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, message: 2 //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--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) 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.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,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+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();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ 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: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+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.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 heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+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();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ 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.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: 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: 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: '',
+ withCredentials: false,*/
+ 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;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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.withCredentials) && '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();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].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) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+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/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+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.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+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;
+
+};
+
+/*
+---
+
+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);
+});
+
+})();
+
+/*
+---
+
+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,
+ compensateScroll: 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.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ 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),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ 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(offsetParent);
+ 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 = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ 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];
+ }
+ },
+
+ 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);
+ this.mouse.start = null;
+ 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 = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['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 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ 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: 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: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+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;
+ }
+
+});
+
+/*
+---
+
+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));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+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: 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: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+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('&');
+ }
+
+});
+
+/*
+---
+
+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: 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: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+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: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+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;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(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 ('ActiveXObject' in window){
+ 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);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/Purr/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/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Flat/lib/MooTools/TinyTab/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/tml/admin.html b/pyload/webui/themes/Flat/tml/admin.html
new file mode 100644
index 000000000..c5cdb494b
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/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..06061e705
--- /dev/null
+++ b/pyload/webui/themes/Flat/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="/css/base.css"/>
+<link rel="stylesheet" type="text/css" href="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/>
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/js/base.min.js"></script>
+
+<title>{% block title %}pyLoad {{_("Web UI")}}{% 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="/img/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="/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="/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="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('filemanager') }}>
+ <a href="/filemanager/" title=""><img src="/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Settings")}}</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 - {{_("Web UI")}}{% endblock %}</h1>
+
+{% block statusbar %}
+{% endblock %}
+
+
+<br/>
+
+<div class="level1" style="clear:both">
+</div>
+<noscript><h1>Enable JavaScript to use the web ui.</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="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2015 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 '/tml/window.html' %}
+ {% include '/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..3bfa6cbcf
--- /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>
diff --git a/pyload/webui/themes/Flat/tml/downloads.html b/pyload/webui/themes/Flat/tml/downloads.html
new file mode 100644
index 000000000..f6e581c5b
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/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/Flat/tml/filemanager.html b/pyload/webui/themes/Flat/tml/filemanager.html
new file mode 100644
index 000000000..b4872b084
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/filemanager.html
@@ -0,0 +1,78 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+
+<script type="text/javascript" src="/js/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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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/Flat/tml/folder.html b/pyload/webui/themes/Flat/tml/folder.html
new file mode 100644
index 000000000..21f3f4a03
--- /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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/Flat/tml/home.html b/pyload/webui/themes/Flat/tml/home.html
new file mode 100644
index 000000000..cc0bf330c
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/home.html
@@ -0,0 +1,266 @@
+{% extends '/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': '/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="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li>
+ <a href="/filemanager/" title=""><img src="/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/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="/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/Flat/tml/info.html b/pyload/webui/themes/Flat/tml/info.html
new file mode 100644
index 000000000..57e688e09
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/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 %}{{ _("Info") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Info") }}{% 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>{{ _("Web UI Port:") }}</td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Remote Interface Port:") }}</td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Flat/tml/login.html b/pyload/webui/themes/Flat/tml/login.html
new file mode 100644
index 000000000..74bbb70a4
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/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..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/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/Flat/tml/logs.html b/pyload/webui/themes/Flat/tml/logs.html
new file mode 100644
index 000000000..429306aae
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/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 %}
diff --git a/pyload/webui/themes/Flat/tml/pathchooser.html b/pyload/webui/themes/Flat/tml/pathchooser.html
new file mode 100644
index 000000000..6e58ab536
--- /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="/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>
diff --git a/pyload/webui/themes/Flat/tml/queue.html b/pyload/webui/themes/Flat/tml/queue.html
new file mode 100644
index 000000000..8038a085b
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.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="/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="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/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 %}
diff --git a/pyload/webui/themes/Flat/tml/settings.html b/pyload/webui/themes/Flat/tml/settings.html
new file mode 100644
index 000000000..47f69b188
--- /dev/null
+++ b/pyload/webui/themes/Flat/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Settings") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Settings") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/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 %}
diff --git a/pyload/webui/themes/Flat/tml/settings_item.html b/pyload/webui/themes/Flat/tml/settings_item.html
new file mode 100644
index 000000000..71c212304
--- /dev/null
+++ b/pyload/webui/themes/Flat/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 sorted_conf(section) %}
+ {% 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/Flat/tml/window.html b/pyload/webui/themes/Flat/tml/window.html
new file mode 100644
index 000000000..023441f69
--- /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="/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/Next/css/MooDialog.css b/pyload/webui/themes/Next/css/MooDialog.css
new file mode 100644
index 000000000..d7c827e13
--- /dev/null
+++ b/pyload/webui/themes/Next/css/MooDialog.css
@@ -0,0 +1,92 @@
+/* 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: #fff;
+ 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(../lib/MooTools/MooDialog/css/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(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat;
+}
+
diff --git a/pyload/webui/themes/Next/css/log.css b/pyload/webui/themes/Next/css/log.css
new file mode 100644
index 000000000..30e026821
--- /dev/null
+++ b/pyload/webui/themes/Next/css/log.css
@@ -0,0 +1,112 @@
+
+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;
+}
+td.MixedDEBUG, td.LineDEBUG, td.loglevelLineDEBUG
+{
+ color: darkcyan;
+}
+td.MixedWARNING, td.LineWARNING, td.loglevelLineWARNING
+{
+ color: #660;
+}
+td.MixedERROR, td.LineERROR, td.loglevelLineERROR
+{
+ color: red;
+}
+td.MixedCRITICAL, td.LineCRITICAL, td.loglevelLineCRITICAL
+{
+ color: purple;
+}
+td.loglevelMixedDEBUG span, td.loglevelLabelDEBUG span
+{
+ font-weight: bold;
+ color: white;
+ background-color: darkcyan;
+}
+td.loglevelMixedWARNING span, td.loglevelLabelWARNING span
+{
+ font-weight: bold;
+ color: white;
+ background-color: #660;
+}
+td.loglevelMixedERROR span, td.loglevelLabelERROR span
+{
+ font-weight: bold;
+ color: white;
+ background-color: red;
+}
+td.loglevelMixedCRITICAL span, td.loglevelLabelCRITICAL span
+{
+ font-weight: bold;
+ color: white;
+ background-color: purple;
+}
+.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/Next/css/pathchooser.css b/pyload/webui/themes/Next/css/pathchooser.css
new file mode 100644
index 000000000..bfd84b19b
--- /dev/null
+++ b/pyload/webui/themes/Next/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: #fff;
+}
+
+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/Next/css/window.css
index 12829868b..12829868b 100644
--- a/module/web/media/default/css/window.css
+++ b/pyload/webui/themes/Next/css/window.css
diff --git a/pyload/webui/themes/Next/img/add_folder.png b/pyload/webui/themes/Next/img/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/Next/img/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/ajax-loader.gif b/pyload/webui/themes/Next/img/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/Next/img/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/Next/img/arrow_refresh.png b/pyload/webui/themes/Next/img/arrow_refresh.png
new file mode 100644
index 000000000..0de26566d
--- /dev/null
+++ b/pyload/webui/themes/Next/img/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/button.png b/pyload/webui/themes/Next/img/button.png
new file mode 100644
index 000000000..bb408a7d6
--- /dev/null
+++ b/pyload/webui/themes/Next/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/control_cancel.png b/pyload/webui/themes/Next/img/control_cancel.png
new file mode 100644
index 000000000..7b9bc3fba
--- /dev/null
+++ b/pyload/webui/themes/Next/img/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/delete.png b/pyload/webui/themes/Next/img/delete.png
new file mode 100644
index 000000000..08f249365
--- /dev/null
+++ b/pyload/webui/themes/Next/img/delete.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/package_go.png b/pyload/webui/themes/Next/img/package_go.png
new file mode 100644
index 000000000..aace63ad6
--- /dev/null
+++ b/pyload/webui/themes/Next/img/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/pencil.png b/pyload/webui/themes/Next/img/pencil.png
new file mode 100644
index 000000000..0bfecd50e
--- /dev/null
+++ b/pyload/webui/themes/Next/img/pencil.png
Binary files differ
diff --git a/pyload/webui/themes/Next/img/pyload-logo.png b/pyload/webui/themes/Next/img/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ b/pyload/webui/themes/Next/img/pyload-logo.png
Binary files differ
diff --git a/module/web/media/js/admin.coffee b/pyload/webui/themes/Next/js/admin.coffee
index 82b0dd3ec..82b0dd3ec 100644
--- a/module/web/media/js/admin.coffee
+++ b/pyload/webui/themes/Next/js/admin.coffee
diff --git a/module/web/media/js/admin.js b/pyload/webui/themes/Next/js/admin.js
index d34d310a0..d34d310a0 100644
--- a/module/web/media/js/admin.js
+++ b/pyload/webui/themes/Next/js/admin.js
diff --git a/pyload/webui/themes/Next/js/base.coffee b/pyload/webui/themes/Next/js/base.coffee
new file mode 100644
index 000000000..f58c1e4cf
--- /dev/null
+++ b/pyload/webui/themes/Next/js/base.coffee
@@ -0,0 +1,173 @@
+# 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/pyload/webui/themes/Next/js/base.js
index c68b1047a..c68b1047a 100644
--- a/module/web/media/js/base.js
+++ b/pyload/webui/themes/Next/js/base.js
diff --git a/pyload/webui/themes/Next/js/filemanager.js b/pyload/webui/themes/Next/js/filemanager.js
new file mode 100644
index 000000000..32c225e4a
--- /dev/null
+++ b/pyload/webui/themes/Next/js/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/Next/js/package.js b/pyload/webui/themes/Next/js/package.js
new file mode 100644
index 000000000..b5d711f28
--- /dev/null
+++ b/pyload/webui/themes/Next/js/package.js
@@ -0,0 +1,397 @@
+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('span');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[3].addEvent('click', this.deletePackage.bind(this));
+ imgs[4].addEvent('click', this.restartPackage.bind(this));
+ imgs[5].addEvent('click', this.editPackage.bind(this));
+ imgs[6].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
+ }
+ });
+
+ if (link.icon == 'arrow_right.png'){
+ link.icon = 'glyphicon glyphicon-arrow-right';
+ }
+ if (link.icon == 'status_downloading.png'){
+ link.icon = 'glyphicon glyphicon-cloud-download';
+ }
+ if (link.icon == 'status_failed.png'){
+ link.icon = 'glyphicon glyphicon-exclamation-sign';
+ }
+ if (link.icon == 'status_finished.png'){
+ link.icon = 'glyphicon glyphicon-ok';
+ }
+ if (link.statusmsg == 'queued'){
+ link.icon = 'glyphicon glyphicon-time';
+ }
+ if (link.icon == 'status_offline.png'){
+ link.icon = 'glyphicon glyphicon-ban-circle';
+ }
+
+
+ var html = "<span style='' class='child_status'><span style='margin-right: 2px;' class='{icon} sorthandle'></span></span>\n".substitute({"icon": link.icon});
+ html += "<span style='font-size: 18px; text-weight:bold'>{name}</span><br /><div class='child_secrow' style='margin-left: 21px; margin-bottom: 7px;'>".substitute({"name": link.name});
+ html += "<span class='child_status' style='font-size: 12px; color:#555'>{statusmsg}</span>{error}&nbsp;".substitute({"statusmsg": link.statusmsg, "error":link.error});
+ html += "<span class='child_status' style='font-size: 12px; color:#555'>{format_size}</span>".substitute({"format_size": link.format_size});
+ html += "<span class='child_status' style='font-size: 12px; color:#555'> {plugin}</span>&nbsp;&nbsp;".substitute({"plugin": link.plugin});
+ html += "<span class='glyphicon glyphicon-trash' title='{{_("Delete Link")}}' style='cursor: pointer; font-size: 12px; color:#333;' ></span>&nbsp;&nbsp;";
+ html += "<span class='glyphicon glyphicon-repeat' title='{{_("Restart Link")}}' style='cursor: pointer; font-size: 12px; color:#333;' ></span></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 span');
+ imgs[3].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[4].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements(".glyphicon");
+ imgs[0].set("class", "glyphicon glyphicon-time");
+ 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/Next/js/settings.coffee b/pyload/webui/themes/Next/js/settings.coffee
new file mode 100644
index 000000000..0fc908d46
--- /dev/null
+++ b/pyload/webui/themes/Next/js/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() \ No newline at end of file
diff --git a/pyload/webui/themes/Next/js/settings.js b/pyload/webui/themes/Next/js/settings.js
new file mode 100644
index 000000000..be694d365
--- /dev/null
+++ b/pyload/webui/themes/Next/js/settings.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"),$$("#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("show").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/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css
new file mode 100644
index 000000000..bb663496d
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css
@@ -0,0 +1,476 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+}
+.btn-default:active,
+.btn-primary:active,
+.btn-success:active,
+.btn-info:active,
+.btn-warning:active,
+.btn-danger:active,
+.btn-default.active,
+.btn-primary.active,
+.btn-success.active,
+.btn-info.active,
+.btn-warning.active,
+.btn-danger.active {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn-default .badge,
+.btn-primary .badge,
+.btn-success .badge,
+.btn-info .badge,
+.btn-warning .badge,
+.btn-danger .badge {
+ text-shadow: none;
+}
+.btn:active,
+.btn.active {
+ background-image: none;
+}
+.btn-default {
+ text-shadow: 0 1px 0 #fff;
+ background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+ background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
+ background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #dbdbdb;
+ border-color: #ccc;
+}
+.btn-default:hover,
+.btn-default:focus {
+ background-color: #e0e0e0;
+ background-position: 0 -15px;
+}
+.btn-default:active,
+.btn-default.active {
+ background-color: #e0e0e0;
+ border-color: #dbdbdb;
+}
+.btn-default.disabled,
+.btn-default:disabled,
+.btn-default[disabled] {
+ background-color: #e0e0e0;
+ background-image: none;
+}
+.btn-primary {
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #245580;
+}
+.btn-primary:hover,
+.btn-primary:focus {
+ background-color: #265a88;
+ background-position: 0 -15px;
+}
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #265a88;
+ border-color: #245580;
+}
+.btn-primary.disabled,
+.btn-primary:disabled,
+.btn-primary[disabled] {
+ background-color: #265a88;
+ background-image: none;
+}
+.btn-success {
+ background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
+ background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #3e8f3e;
+}
+.btn-success:hover,
+.btn-success:focus {
+ background-color: #419641;
+ background-position: 0 -15px;
+}
+.btn-success:active,
+.btn-success.active {
+ background-color: #419641;
+ border-color: #3e8f3e;
+}
+.btn-success.disabled,
+.btn-success:disabled,
+.btn-success[disabled] {
+ background-color: #419641;
+ background-image: none;
+}
+.btn-info {
+ background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+ background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #28a4c9;
+}
+.btn-info:hover,
+.btn-info:focus {
+ background-color: #2aabd2;
+ background-position: 0 -15px;
+}
+.btn-info:active,
+.btn-info.active {
+ background-color: #2aabd2;
+ border-color: #28a4c9;
+}
+.btn-info.disabled,
+.btn-info:disabled,
+.btn-info[disabled] {
+ background-color: #2aabd2;
+ background-image: none;
+}
+.btn-warning {
+ background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+ background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #e38d13;
+}
+.btn-warning:hover,
+.btn-warning:focus {
+ background-color: #eb9316;
+ background-position: 0 -15px;
+}
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #eb9316;
+ border-color: #e38d13;
+}
+.btn-warning.disabled,
+.btn-warning:disabled,
+.btn-warning[disabled] {
+ background-color: #eb9316;
+ background-image: none;
+}
+.btn-danger {
+ background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+ background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-color: #b92c28;
+}
+.btn-danger:hover,
+.btn-danger:focus {
+ background-color: #c12e2a;
+ background-position: 0 -15px;
+}
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #c12e2a;
+ border-color: #b92c28;
+}
+.btn-danger.disabled,
+.btn-danger:disabled,
+.btn-danger[disabled] {
+ background-color: #c12e2a;
+ background-image: none;
+}
+.thumbnail,
+.img-thumbnail {
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+ background-color: #e8e8e8;
+ background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
+ background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+ background-repeat: repeat-x;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+ background-color: #2e6da4;
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+ background-repeat: repeat-x;
+}
+.navbar-default {
+ background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
+ background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
+ background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .active > a {
+ background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
+ background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
+ background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
+ background-repeat: repeat-x;
+ -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+}
+.navbar-brand,
+.navbar-nav > li > a {
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
+}
+.navbar-inverse {
+ background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
+ background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
+ background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ background-repeat: repeat-x;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .active > a {
+ background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
+ background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
+ background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
+ background-repeat: repeat-x;
+ -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+ box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+}
+.navbar-inverse .navbar-brand,
+.navbar-inverse .navbar-nav > li > a {
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
+}
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ border-radius: 0;
+}
+@media (max-width: 767px) {
+ .navbar .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #fff;
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+ background-repeat: repeat-x;
+ }
+}
+.alert {
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+}
+.alert-success {
+ background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+ background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #b2dba1;
+}
+.alert-info {
+ background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+ background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #9acfea;
+}
+.alert-warning {
+ background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+ background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #f5e79e;
+}
+.alert-danger {
+ background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+ background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
+ background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #dca7a7;
+}
+.progress {
+ background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+ background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
+ background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar {
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-success {
+ background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+ background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-info {
+ background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+ background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-warning {
+ background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+ background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-danger {
+ background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+ background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+ background-repeat: repeat-x;
+}
+.progress-bar-striped {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.list-group {
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+ text-shadow: 0 -1px 0 #286090;
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #2b669a;
+}
+.list-group-item.active .badge,
+.list-group-item.active:hover .badge,
+.list-group-item.active:focus .badge {
+ text-shadow: none;
+}
+.panel {
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+}
+.panel-default > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
+ background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-primary > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
+ background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-success > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+ background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-info > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+ background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-warning > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+ background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
+ background-repeat: repeat-x;
+}
+.panel-danger > .panel-heading {
+ background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+ background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
+ background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
+ background-repeat: repeat-x;
+}
+.well {
+ background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+ background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
+ background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
+ background-repeat: repeat-x;
+ border-color: #dcdcdc;
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+}
+/*# sourceMappingURL=bootstrap-theme.css.map */
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map
new file mode 100644
index 000000000..5a12d6317
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAcA;;;;;;EAME,0CAAA;ECgDA,6FAAA;EACQ,qFAAA;EC5DT;AFgBC;;;;;;;;;;;;EC2CA,0DAAA;EACQ,kDAAA;EC7CT;AFVD;;;;;;EAiBI,mBAAA;EECH;AFiCC;;EAEE,wBAAA;EE/BH;AFoCD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EAgC2C,2BAAA;EAA2B,oBAAA;EEzBvE;AFLC;;EAEE,2BAAA;EACA,8BAAA;EEOH;AFJC;;EAEE,2BAAA;EACA,uBAAA;EEMH;AFHC;;;EAGE,2BAAA;EACA,wBAAA;EEKH;AFUD;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEgCD;AF9BC;;EAEE,2BAAA;EACA,8BAAA;EEgCH;AF7BC;;EAEE,2BAAA;EACA,uBAAA;EE+BH;AF5BC;;;EAGE,2BAAA;EACA,wBAAA;EE8BH;AFdD;EGrDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEyDD;AFvDC;;EAEE,2BAAA;EACA,8BAAA;EEyDH;AFtDC;;EAEE,2BAAA;EACA,uBAAA;EEwDH;AFrDC;;;EAGE,2BAAA;EACA,wBAAA;EEuDH;AFtCD;EGtDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEkFD;AFhFC;;EAEE,2BAAA;EACA,8BAAA;EEkFH;AF/EC;;EAEE,2BAAA;EACA,uBAAA;EEiFH;AF9EC;;;EAGE,2BAAA;EACA,wBAAA;EEgFH;AF9DD;EGvDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EE2GD;AFzGC;;EAEE,2BAAA;EACA,8BAAA;EE2GH;AFxGC;;EAEE,2BAAA;EACA,uBAAA;EE0GH;AFvGC;;;EAGE,2BAAA;EACA,wBAAA;EEyGH;AFtFD;EGxDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEoID;AFlIC;;EAEE,2BAAA;EACA,8BAAA;EEoIH;AFjIC;;EAEE,2BAAA;EACA,uBAAA;EEmIH;AFhIC;;;EAGE,2BAAA;EACA,wBAAA;EEkIH;AFxGD;;EChBE,oDAAA;EACQ,4CAAA;EC4HT;AFnGD;;EGzEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHwEF,2BAAA;EEyGD;AFvGD;;;EG9EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8EF,2BAAA;EE6GD;AFpGD;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ6GA,oBAAA;EC/CA,6FAAA;EACQ,qFAAA;EC0JT;AF/GD;;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECoKT;AF5GD;;EAEE,gDAAA;EE8GD;AF1GD;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EF+OD;AFlHD;;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0LT;AF5HD;;EAYI,2CAAA;EEoHH;AF/GD;;;EAGE,kBAAA;EEiHD;AF5FD;EAfI;;;IAGE,aAAA;IG3IF,0EAAA;IACA,qEAAA;IACA,+FAAA;IAAA,wEAAA;IACA,6BAAA;IACA,wHAAA;ID0PD;EACF;AFxGD;EACE,+CAAA;ECzGA,4FAAA;EACQ,oFAAA;ECoNT;AFhGD;EGpKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4GD;AFvGD;EGrKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoHD;AF9GD;EGtKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4HD;AFrHD;EGvKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoID;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AFlHD;EGzLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AFxHD;EG1LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqTH;AF9HD;EG3LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4TH;AFpID;EG5LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmUH;AF1ID;EG7LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0UH;AF7ID;EGhKI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDgTH;AFzID;EACE,oBAAA;EC5JA,oDAAA;EACQ,4CAAA;ECwST;AF1ID;;;EAGE,+BAAA;EGjNE,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH+MF,uBAAA;EEgJD;AFrJD;;;EAQI,mBAAA;EEkJH;AFxID;ECjLE,mDAAA;EACQ,2CAAA;EC4TT;AFlID;EG1OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED+WH;AFxID;EG3OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDsXH;AF9ID;EG5OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED6XH;AFpJD;EG7OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDoYH;AF1JD;EG9OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2YH;AFhKD;EG/OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkZH;AFhKD;EGtPI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHoPF,uBAAA;ECzMA,2FAAA;EACQ,mFAAA;ECgXT","file":"bootstrap-theme.css","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &:disabled,\n &[disabled] {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n",".btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default:disabled,\n.btn-default[disabled] {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary:disabled,\n.btn-primary[disabled] {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success:disabled,\n.btn-success[disabled] {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info:disabled,\n.btn-info[disabled] {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning:disabled,\n.btn-warning[disabled] {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger:disabled,\n.btn-danger[disabled] {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css
new file mode 100644
index 000000000..ac8dd5505
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css
new file mode 100644
index 000000000..c46af7dfb
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css
@@ -0,0 +1,6566 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+html {
+ font-family: sans-serif;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+body {
+ margin: 0;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+audio,
+canvas,
+progress,
+video {
+ display: inline-block;
+ vertical-align: baseline;
+}
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+[hidden],
+template {
+ display: none;
+}
+a {
+ background-color: transparent;
+}
+a:active,
+a:hover {
+ outline: 0;
+}
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+b,
+strong {
+ font-weight: bold;
+}
+dfn {
+ font-style: italic;
+}
+h1 {
+ margin: .67em 0;
+ font-size: 2em;
+}
+mark {
+ color: #000;
+ background: #ff0;
+}
+small {
+ font-size: 80%;
+}
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+sup {
+ top: -.5em;
+}
+sub {
+ bottom: -.25em;
+}
+img {
+ border: 0;
+}
+svg:not(:root) {
+ overflow: hidden;
+}
+figure {
+ margin: 1em 40px;
+}
+hr {
+ height: 0;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+pre {
+ overflow: auto;
+}
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+ margin: 0;
+ font: inherit;
+ color: inherit;
+}
+button {
+ overflow: visible;
+}
+button,
+select {
+ text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button;
+ cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+input {
+ line-height: normal;
+}
+input[type="checkbox"],
+input[type="radio"] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: 0;
+}
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+input[type="search"] {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ -webkit-appearance: textfield;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+fieldset {
+ padding: .35em .625em .75em;
+ margin: 0 2px;
+ border: 1px solid #c0c0c0;
+}
+legend {
+ padding: 0;
+ border: 0;
+}
+textarea {
+ overflow: auto;
+}
+optgroup {
+ font-weight: bold;
+}
+table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+td,
+th {
+ padding: 0;
+}
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+@media print {
+ *,
+ *:before,
+ *:after {
+ color: #000 !important;
+ text-shadow: none !important;
+ background: transparent !important;
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important;
+ }
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+ a[href]:after {
+ content: " (" attr(href) ")";
+ }
+ abbr[title]:after {
+ content: " (" attr(title) ")";
+ }
+ a[href^="#"]:after,
+ a[href^="javascript:"]:after {
+ content: "";
+ }
+ pre,
+ blockquote {
+ border: 1px solid #999;
+
+ page-break-inside: avoid;
+ }
+ thead {
+ display: table-header-group;
+ }
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+ img {
+ max-width: 100% !important;
+ }
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+ select {
+ background: #fff !important;
+ }
+ .navbar {
+ display: none;
+ }
+ .btn > .caret,
+ .dropup > .btn > .caret {
+ border-top-color: #000 !important;
+ }
+ .label {
+ border: 1px solid #000;
+ }
+ .table {
+ border-collapse: collapse !important;
+ }
+ .table td,
+ .table th {
+ background-color: #fff !important;
+ }
+ .table-bordered th,
+ .table-bordered td {
+ border: 1px solid #ddd !important;
+ }
+}
+@font-face {
+ font-family: 'Glyphicons Halflings';
+
+ src: url('../fonts/glyphicons-halflings-regular.eot');
+ src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+}
+.glyphicon {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+.glyphicon-asterisk:before {
+ content: "\2a";
+}
+.glyphicon-plus:before {
+ content: "\2b";
+}
+.glyphicon-euro:before,
+.glyphicon-eur:before {
+ content: "\20ac";
+}
+.glyphicon-minus:before {
+ content: "\2212";
+}
+.glyphicon-cloud:before {
+ content: "\2601";
+}
+.glyphicon-envelope:before {
+ content: "\2709";
+}
+.glyphicon-pencil:before {
+ content: "\270f";
+}
+.glyphicon-glass:before {
+ content: "\e001";
+}
+.glyphicon-music:before {
+ content: "\e002";
+}
+.glyphicon-search:before {
+ content: "\e003";
+}
+.glyphicon-heart:before {
+ content: "\e005";
+}
+.glyphicon-star:before {
+ content: "\e006";
+}
+.glyphicon-star-empty:before {
+ content: "\e007";
+}
+.glyphicon-user:before {
+ content: "\e008";
+}
+.glyphicon-film:before {
+ content: "\e009";
+}
+.glyphicon-th-large:before {
+ content: "\e010";
+}
+.glyphicon-th:before {
+ content: "\e011";
+}
+.glyphicon-th-list:before {
+ content: "\e012";
+}
+.glyphicon-ok:before {
+ content: "\e013";
+}
+.glyphicon-remove:before {
+ content: "\e014";
+}
+.glyphicon-zoom-in:before {
+ content: "\e015";
+}
+.glyphicon-zoom-out:before {
+ content: "\e016";
+}
+.glyphicon-off:before {
+ content: "\e017";
+}
+.glyphicon-signal:before {
+ content: "\e018";
+}
+.glyphicon-cog:before {
+ content: "\e019";
+}
+.glyphicon-trash:before {
+ content: "\e020";
+}
+.glyphicon-home:before {
+ content: "\e021";
+}
+.glyphicon-file:before {
+ content: "\e022";
+}
+.glyphicon-time:before {
+ content: "\e023";
+}
+.glyphicon-road:before {
+ content: "\e024";
+}
+.glyphicon-download-alt:before {
+ content: "\e025";
+}
+.glyphicon-download:before {
+ content: "\e026";
+}
+.glyphicon-upload:before {
+ content: "\e027";
+}
+.glyphicon-inbox:before {
+ content: "\e028";
+}
+.glyphicon-play-circle:before {
+ content: "\e029";
+}
+.glyphicon-repeat:before {
+ content: "\e030";
+}
+.glyphicon-refresh:before {
+ content: "\e031";
+}
+.glyphicon-list-alt:before {
+ content: "\e032";
+}
+.glyphicon-lock:before {
+ content: "\e033";
+}
+.glyphicon-flag:before {
+ content: "\e034";
+}
+.glyphicon-headphones:before {
+ content: "\e035";
+}
+.glyphicon-volume-off:before {
+ content: "\e036";
+}
+.glyphicon-volume-down:before {
+ content: "\e037";
+}
+.glyphicon-volume-up:before {
+ content: "\e038";
+}
+.glyphicon-qrcode:before {
+ content: "\e039";
+}
+.glyphicon-barcode:before {
+ content: "\e040";
+}
+.glyphicon-tag:before {
+ content: "\e041";
+}
+.glyphicon-tags:before {
+ content: "\e042";
+}
+.glyphicon-book:before {
+ content: "\e043";
+}
+.glyphicon-bookmark:before {
+ content: "\e044";
+}
+.glyphicon-print:before {
+ content: "\e045";
+}
+.glyphicon-camera:before {
+ content: "\e046";
+}
+.glyphicon-font:before {
+ content: "\e047";
+}
+.glyphicon-bold:before {
+ content: "\e048";
+}
+.glyphicon-italic:before {
+ content: "\e049";
+}
+.glyphicon-text-height:before {
+ content: "\e050";
+}
+.glyphicon-text-width:before {
+ content: "\e051";
+}
+.glyphicon-align-left:before {
+ content: "\e052";
+}
+.glyphicon-align-center:before {
+ content: "\e053";
+}
+.glyphicon-align-right:before {
+ content: "\e054";
+}
+.glyphicon-align-justify:before {
+ content: "\e055";
+}
+.glyphicon-list:before {
+ content: "\e056";
+}
+.glyphicon-indent-left:before {
+ content: "\e057";
+}
+.glyphicon-indent-right:before {
+ content: "\e058";
+}
+.glyphicon-facetime-video:before {
+ content: "\e059";
+}
+.glyphicon-picture:before {
+ content: "\e060";
+}
+.glyphicon-map-marker:before {
+ content: "\e062";
+}
+.glyphicon-adjust:before {
+ content: "\e063";
+}
+.glyphicon-tint:before {
+ content: "\e064";
+}
+.glyphicon-edit:before {
+ content: "\e065";
+}
+.glyphicon-share:before {
+ content: "\e066";
+}
+.glyphicon-check:before {
+ content: "\e067";
+}
+.glyphicon-move:before {
+ content: "\e068";
+}
+.glyphicon-step-backward:before {
+ content: "\e069";
+}
+.glyphicon-fast-backward:before {
+ content: "\e070";
+}
+.glyphicon-backward:before {
+ content: "\e071";
+}
+.glyphicon-play:before {
+ content: "\e072";
+}
+.glyphicon-pause:before {
+ content: "\e073";
+}
+.glyphicon-stop:before {
+ content: "\e074";
+}
+.glyphicon-forward:before {
+ content: "\e075";
+}
+.glyphicon-fast-forward:before {
+ content: "\e076";
+}
+.glyphicon-step-forward:before {
+ content: "\e077";
+}
+.glyphicon-eject:before {
+ content: "\e078";
+}
+.glyphicon-chevron-left:before {
+ content: "\e079";
+}
+.glyphicon-chevron-right:before {
+ content: "\e080";
+}
+.glyphicon-plus-sign:before {
+ content: "\e081";
+}
+.glyphicon-minus-sign:before {
+ content: "\e082";
+}
+.glyphicon-remove-sign:before {
+ content: "\e083";
+}
+.glyphicon-ok-sign:before {
+ content: "\e084";
+}
+.glyphicon-question-sign:before {
+ content: "\e085";
+}
+.glyphicon-info-sign:before {
+ content: "\e086";
+}
+.glyphicon-screenshot:before {
+ content: "\e087";
+}
+.glyphicon-remove-circle:before {
+ content: "\e088";
+}
+.glyphicon-ok-circle:before {
+ content: "\e089";
+}
+.glyphicon-ban-circle:before {
+ content: "\e090";
+}
+.glyphicon-arrow-left:before {
+ content: "\e091";
+}
+.glyphicon-arrow-right:before {
+ content: "\e092";
+}
+.glyphicon-arrow-up:before {
+ content: "\e093";
+}
+.glyphicon-arrow-down:before {
+ content: "\e094";
+}
+.glyphicon-share-alt:before {
+ content: "\e095";
+}
+.glyphicon-resize-full:before {
+ content: "\e096";
+}
+.glyphicon-resize-small:before {
+ content: "\e097";
+}
+.glyphicon-exclamation-sign:before {
+ content: "\e101";
+}
+.glyphicon-gift:before {
+ content: "\e102";
+}
+.glyphicon-leaf:before {
+ content: "\e103";
+}
+.glyphicon-fire:before {
+ content: "\e104";
+}
+.glyphicon-eye-open:before {
+ content: "\e105";
+}
+.glyphicon-eye-close:before {
+ content: "\e106";
+}
+.glyphicon-warning-sign:before {
+ content: "\e107";
+}
+.glyphicon-plane:before {
+ content: "\e108";
+}
+.glyphicon-calendar:before {
+ content: "\e109";
+}
+.glyphicon-random:before {
+ content: "\e110";
+}
+.glyphicon-comment:before {
+ content: "\e111";
+}
+.glyphicon-magnet:before {
+ content: "\e112";
+}
+.glyphicon-chevron-up:before {
+ content: "\e113";
+}
+.glyphicon-chevron-down:before {
+ content: "\e114";
+}
+.glyphicon-retweet:before {
+ content: "\e115";
+}
+.glyphicon-shopping-cart:before {
+ content: "\e116";
+}
+.glyphicon-folder-close:before {
+ content: "\e117";
+}
+.glyphicon-folder-open:before {
+ content: "\e118";
+}
+.glyphicon-resize-vertical:before {
+ content: "\e119";
+}
+.glyphicon-resize-horizontal:before {
+ content: "\e120";
+}
+.glyphicon-hdd:before {
+ content: "\e121";
+}
+.glyphicon-bullhorn:before {
+ content: "\e122";
+}
+.glyphicon-bell:before {
+ content: "\e123";
+}
+.glyphicon-certificate:before {
+ content: "\e124";
+}
+.glyphicon-thumbs-up:before {
+ content: "\e125";
+}
+.glyphicon-thumbs-down:before {
+ content: "\e126";
+}
+.glyphicon-hand-right:before {
+ content: "\e127";
+}
+.glyphicon-hand-left:before {
+ content: "\e128";
+}
+.glyphicon-hand-up:before {
+ content: "\e129";
+}
+.glyphicon-hand-down:before {
+ content: "\e130";
+}
+.glyphicon-circle-arrow-right:before {
+ content: "\e131";
+}
+.glyphicon-circle-arrow-left:before {
+ content: "\e132";
+}
+.glyphicon-circle-arrow-up:before {
+ content: "\e133";
+}
+.glyphicon-circle-arrow-down:before {
+ content: "\e134";
+}
+.glyphicon-globe:before {
+ content: "\e135";
+}
+.glyphicon-wrench:before {
+ content: "\e136";
+}
+.glyphicon-tasks:before {
+ content: "\e137";
+}
+.glyphicon-filter:before {
+ content: "\e138";
+}
+.glyphicon-briefcase:before {
+ content: "\e139";
+}
+.glyphicon-fullscreen:before {
+ content: "\e140";
+}
+.glyphicon-dashboard:before {
+ content: "\e141";
+}
+.glyphicon-paperclip:before {
+ content: "\e142";
+}
+.glyphicon-heart-empty:before {
+ content: "\e143";
+}
+.glyphicon-link:before {
+ content: "\e144";
+}
+.glyphicon-phone:before {
+ content: "\e145";
+}
+.glyphicon-pushpin:before {
+ content: "\e146";
+}
+.glyphicon-usd:before {
+ content: "\e148";
+}
+.glyphicon-gbp:before {
+ content: "\e149";
+}
+.glyphicon-sort:before {
+ content: "\e150";
+}
+.glyphicon-sort-by-alphabet:before {
+ content: "\e151";
+}
+.glyphicon-sort-by-alphabet-alt:before {
+ content: "\e152";
+}
+.glyphicon-sort-by-order:before {
+ content: "\e153";
+}
+.glyphicon-sort-by-order-alt:before {
+ content: "\e154";
+}
+.glyphicon-sort-by-attributes:before {
+ content: "\e155";
+}
+.glyphicon-sort-by-attributes-alt:before {
+ content: "\e156";
+}
+.glyphicon-unchecked:before {
+ content: "\e157";
+}
+.glyphicon-expand:before {
+ content: "\e158";
+}
+.glyphicon-collapse-down:before {
+ content: "\e159";
+}
+.glyphicon-collapse-up:before {
+ content: "\e160";
+}
+.glyphicon-log-in:before {
+ content: "\e161";
+}
+.glyphicon-flash:before {
+ content: "\e162";
+}
+.glyphicon-log-out:before {
+ content: "\e163";
+}
+.glyphicon-new-window:before {
+ content: "\e164";
+}
+.glyphicon-record:before {
+ content: "\e165";
+}
+.glyphicon-save:before {
+ content: "\e166";
+}
+.glyphicon-open:before {
+ content: "\e167";
+}
+.glyphicon-saved:before {
+ content: "\e168";
+}
+.glyphicon-import:before {
+ content: "\e169";
+}
+.glyphicon-export:before {
+ content: "\e170";
+}
+.glyphicon-send:before {
+ content: "\e171";
+}
+.glyphicon-floppy-disk:before {
+ content: "\e172";
+}
+.glyphicon-floppy-saved:before {
+ content: "\e173";
+}
+.glyphicon-floppy-remove:before {
+ content: "\e174";
+}
+.glyphicon-floppy-save:before {
+ content: "\e175";
+}
+.glyphicon-floppy-open:before {
+ content: "\e176";
+}
+.glyphicon-credit-card:before {
+ content: "\e177";
+}
+.glyphicon-transfer:before {
+ content: "\e178";
+}
+.glyphicon-cutlery:before {
+ content: "\e179";
+}
+.glyphicon-header:before {
+ content: "\e180";
+}
+.glyphicon-compressed:before {
+ content: "\e181";
+}
+.glyphicon-earphone:before {
+ content: "\e182";
+}
+.glyphicon-phone-alt:before {
+ content: "\e183";
+}
+.glyphicon-tower:before {
+ content: "\e184";
+}
+.glyphicon-stats:before {
+ content: "\e185";
+}
+.glyphicon-sd-video:before {
+ content: "\e186";
+}
+.glyphicon-hd-video:before {
+ content: "\e187";
+}
+.glyphicon-subtitles:before {
+ content: "\e188";
+}
+.glyphicon-sound-stereo:before {
+ content: "\e189";
+}
+.glyphicon-sound-dolby:before {
+ content: "\e190";
+}
+.glyphicon-sound-5-1:before {
+ content: "\e191";
+}
+.glyphicon-sound-6-1:before {
+ content: "\e192";
+}
+.glyphicon-sound-7-1:before {
+ content: "\e193";
+}
+.glyphicon-copyright-mark:before {
+ content: "\e194";
+}
+.glyphicon-registration-mark:before {
+ content: "\e195";
+}
+.glyphicon-cloud-download:before {
+ content: "\e197";
+}
+.glyphicon-cloud-upload:before {
+ content: "\e198";
+}
+.glyphicon-tree-conifer:before {
+ content: "\e199";
+}
+.glyphicon-tree-deciduous:before {
+ content: "\e200";
+}
+.glyphicon-cd:before {
+ content: "\e201";
+}
+.glyphicon-save-file:before {
+ content: "\e202";
+}
+.glyphicon-open-file:before {
+ content: "\e203";
+}
+.glyphicon-level-up:before {
+ content: "\e204";
+}
+.glyphicon-copy:before {
+ content: "\e205";
+}
+.glyphicon-paste:before {
+ content: "\e206";
+}
+.glyphicon-alert:before {
+ content: "\e209";
+}
+.glyphicon-equalizer:before {
+ content: "\e210";
+}
+.glyphicon-king:before {
+ content: "\e211";
+}
+.glyphicon-queen:before {
+ content: "\e212";
+}
+.glyphicon-pawn:before {
+ content: "\e213";
+}
+.glyphicon-bishop:before {
+ content: "\e214";
+}
+.glyphicon-knight:before {
+ content: "\e215";
+}
+.glyphicon-baby-formula:before {
+ content: "\e216";
+}
+.glyphicon-tent:before {
+ content: "\26fa";
+}
+.glyphicon-blackboard:before {
+ content: "\e218";
+}
+.glyphicon-bed:before {
+ content: "\e219";
+}
+.glyphicon-apple:before {
+ content: "\f8ff";
+}
+.glyphicon-erase:before {
+ content: "\e221";
+}
+.glyphicon-hourglass:before {
+ content: "\231b";
+}
+.glyphicon-lamp:before {
+ content: "\e223";
+}
+.glyphicon-duplicate:before {
+ content: "\e224";
+}
+.glyphicon-piggy-bank:before {
+ content: "\e225";
+}
+.glyphicon-scissors:before {
+ content: "\e226";
+}
+.glyphicon-bitcoin:before {
+ content: "\e227";
+}
+.glyphicon-yen:before {
+ content: "\00a5";
+}
+.glyphicon-ruble:before {
+ content: "\20bd";
+}
+.glyphicon-scale:before {
+ content: "\e230";
+}
+.glyphicon-ice-lolly:before {
+ content: "\e231";
+}
+.glyphicon-ice-lolly-tasted:before {
+ content: "\e232";
+}
+.glyphicon-education:before {
+ content: "\e233";
+}
+.glyphicon-option-horizontal:before {
+ content: "\e234";
+}
+.glyphicon-option-vertical:before {
+ content: "\e235";
+}
+.glyphicon-menu-hamburger:before {
+ content: "\e236";
+}
+.glyphicon-modal-window:before {
+ content: "\e237";
+}
+.glyphicon-oil:before {
+ content: "\e238";
+}
+.glyphicon-grain:before {
+ content: "\e239";
+}
+.glyphicon-sunglasses:before {
+ content: "\e240";
+}
+.glyphicon-text-size:before {
+ content: "\e241";
+}
+.glyphicon-text-color:before {
+ content: "\e242";
+}
+.glyphicon-text-background:before {
+ content: "\e243";
+}
+.glyphicon-object-align-top:before {
+ content: "\e244";
+}
+.glyphicon-object-align-bottom:before {
+ content: "\e245";
+}
+.glyphicon-object-align-horizontal:before {
+ content: "\e246";
+}
+.glyphicon-object-align-left:before {
+ content: "\e247";
+}
+.glyphicon-object-align-vertical:before {
+ content: "\e248";
+}
+.glyphicon-object-align-right:before {
+ content: "\e249";
+}
+.glyphicon-triangle-right:before {
+ content: "\e250";
+}
+.glyphicon-triangle-left:before {
+ content: "\e251";
+}
+.glyphicon-triangle-bottom:before {
+ content: "\e252";
+}
+.glyphicon-triangle-top:before {
+ content: "\e253";
+}
+.glyphicon-console:before {
+ content: "\e254";
+}
+.glyphicon-superscript:before {
+ content: "\e255";
+}
+.glyphicon-subscript:before {
+ content: "\e256";
+}
+.glyphicon-menu-left:before {
+ content: "\e257";
+}
+.glyphicon-menu-right:before {
+ content: "\e258";
+}
+.glyphicon-menu-down:before {
+ content: "\e259";
+}
+.glyphicon-menu-up:before {
+ content: "\e260";
+}
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+*:before,
+*:after {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+html {
+ font-size: 10px;
+
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #333;
+ background-color: #fff;
+}
+input,
+button,
+select,
+textarea {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
+a {
+ color: #337ab7;
+ text-decoration: none;
+}
+a:hover,
+a:focus {
+ color: #23527c;
+ text-decoration: underline;
+}
+a:focus {
+ outline: thin dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+figure {
+ margin: 0;
+}
+img {
+ vertical-align: middle;
+}
+.img-responsive,
+.thumbnail > img,
+.thumbnail a > img,
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+ display: block;
+ max-width: 100%;
+ height: auto;
+}
+.img-rounded {
+ border-radius: 6px;
+}
+.img-thumbnail {
+ display: inline-block;
+ max-width: 100%;
+ height: auto;
+ padding: 4px;
+ line-height: 1.42857143;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -webkit-transition: all .2s ease-in-out;
+ -o-transition: all .2s ease-in-out;
+ transition: all .2s ease-in-out;
+}
+.img-circle {
+ border-radius: 50%;
+}
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border: 0;
+ border-top: 1px solid #eee;
+}
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ clip: auto;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+ font-family: inherit;
+ font-weight: 500;
+ line-height: 1.1;
+ color: inherit;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small,
+h1 .small,
+h2 .small,
+h3 .small,
+h4 .small,
+h5 .small,
+h6 .small,
+.h1 .small,
+.h2 .small,
+.h3 .small,
+.h4 .small,
+.h5 .small,
+.h6 .small {
+ font-weight: normal;
+ line-height: 1;
+ color: #777;
+}
+h1,
+.h1,
+h2,
+.h2,
+h3,
+.h3 {
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+h1 small,
+.h1 small,
+h2 small,
+.h2 small,
+h3 small,
+.h3 small,
+h1 .small,
+.h1 .small,
+h2 .small,
+.h2 .small,
+h3 .small,
+.h3 .small {
+ font-size: 65%;
+}
+h4,
+.h4,
+h5,
+.h5,
+h6,
+.h6 {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+h4 small,
+.h4 small,
+h5 small,
+.h5 small,
+h6 small,
+.h6 small,
+h4 .small,
+.h4 .small,
+h5 .small,
+.h5 .small,
+h6 .small,
+.h6 .small {
+ font-size: 75%;
+}
+h1,
+.h1 {
+ font-size: 36px;
+}
+h2,
+.h2 {
+ font-size: 30px;
+}
+h3,
+.h3 {
+ font-size: 24px;
+}
+h4,
+.h4 {
+ font-size: 18px;
+}
+h5,
+.h5 {
+ font-size: 14px;
+}
+h6,
+.h6 {
+ font-size: 12px;
+}
+p {
+ margin: 0 0 10px;
+}
+.lead {
+ margin-bottom: 20px;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 1.4;
+}
+@media (min-width: 768px) {
+ .lead {
+ font-size: 21px;
+ }
+}
+small,
+.small {
+ font-size: 85%;
+}
+mark,
+.mark {
+ padding: .2em;
+ background-color: #fcf8e3;
+}
+.text-left {
+ text-align: left;
+}
+.text-right {
+ text-align: right;
+}
+.text-center {
+ text-align: center;
+}
+.text-justify {
+ text-align: justify;
+}
+.text-nowrap {
+ white-space: nowrap;
+}
+.text-lowercase {
+ text-transform: lowercase;
+}
+.text-uppercase {
+ text-transform: uppercase;
+}
+.text-capitalize {
+ text-transform: capitalize;
+}
+.text-muted {
+ color: #777;
+}
+.text-primary {
+ color: #337ab7;
+}
+a.text-primary:hover {
+ color: #286090;
+}
+.text-success {
+ color: #3c763d;
+}
+a.text-success:hover {
+ color: #2b542c;
+}
+.text-info {
+ color: #31708f;
+}
+a.text-info:hover {
+ color: #245269;
+}
+.text-warning {
+ color: #8a6d3b;
+}
+a.text-warning:hover {
+ color: #66512c;
+}
+.text-danger {
+ color: #a94442;
+}
+a.text-danger:hover {
+ color: #843534;
+}
+.bg-primary {
+ color: #fff;
+ background-color: #337ab7;
+}
+a.bg-primary:hover {
+ background-color: #286090;
+}
+.bg-success {
+ background-color: #dff0d8;
+}
+a.bg-success:hover {
+ background-color: #c1e2b3;
+}
+.bg-info {
+ background-color: #d9edf7;
+}
+a.bg-info:hover {
+ background-color: #afd9ee;
+}
+.bg-warning {
+ background-color: #fcf8e3;
+}
+a.bg-warning:hover {
+ background-color: #f7ecb5;
+}
+.bg-danger {
+ background-color: #f2dede;
+}
+a.bg-danger:hover {
+ background-color: #e4b9b9;
+}
+.page-header {
+ padding-bottom: 9px;
+ margin: 40px 0 20px;
+ border-bottom: 1px solid #eee;
+}
+ul,
+ol {
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+ margin-bottom: 0;
+}
+.list-unstyled {
+ padding-left: 0;
+ list-style: none;
+}
+.list-inline {
+ padding-left: 0;
+ margin-left: -5px;
+ list-style: none;
+}
+.list-inline > li {
+ display: inline-block;
+ padding-right: 5px;
+ padding-left: 5px;
+}
+dl {
+ margin-top: 0;
+ margin-bottom: 20px;
+}
+dt,
+dd {
+ line-height: 1.42857143;
+}
+dt {
+ font-weight: bold;
+}
+dd {
+ margin-left: 0;
+}
+@media (min-width: 768px) {
+ .dl-horizontal dt {
+ float: left;
+ width: 160px;
+ overflow: hidden;
+ clear: left;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .dl-horizontal dd {
+ margin-left: 180px;
+ }
+}
+abbr[title],
+abbr[data-original-title] {
+ cursor: help;
+ border-bottom: 1px dotted #777;
+}
+.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+blockquote {
+ padding: 10px 20px;
+ margin: 0 0 20px;
+ font-size: 17.5px;
+ border-left: 5px solid #eee;
+}
+blockquote p:last-child,
+blockquote ul:last-child,
+blockquote ol:last-child {
+ margin-bottom: 0;
+}
+blockquote footer,
+blockquote small,
+blockquote .small {
+ display: block;
+ font-size: 80%;
+ line-height: 1.42857143;
+ color: #777;
+}
+blockquote footer:before,
+blockquote small:before,
+blockquote .small:before {
+ content: '\2014 \00A0';
+}
+.blockquote-reverse,
+blockquote.pull-right {
+ padding-right: 15px;
+ padding-left: 0;
+ text-align: right;
+ border-right: 5px solid #eee;
+ border-left: 0;
+}
+.blockquote-reverse footer:before,
+blockquote.pull-right footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right small:before,
+.blockquote-reverse .small:before,
+blockquote.pull-right .small:before {
+ content: '';
+}
+.blockquote-reverse footer:after,
+blockquote.pull-right footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right small:after,
+.blockquote-reverse .small:after,
+blockquote.pull-right .small:after {
+ content: '\00A0 \2014';
+}
+address {
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 1.42857143;
+}
+code,
+kbd,
+pre,
+samp {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+code {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #c7254e;
+ background-color: #f9f2f4;
+ border-radius: 4px;
+}
+kbd {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #fff;
+ background-color: #333;
+ border-radius: 3px;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);
+}
+kbd kbd {
+ padding: 0;
+ font-size: 100%;
+ font-weight: bold;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+pre {
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 1.42857143;
+ color: #333;
+ word-break: break-all;
+ word-wrap: break-word;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+pre code {
+ padding: 0;
+ font-size: inherit;
+ color: inherit;
+ white-space: pre-wrap;
+ background-color: transparent;
+ border-radius: 0;
+}
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+.container {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto;
+}
+@media (min-width: 768px) {
+ .container {
+ width: 750px;
+ }
+}
+@media (min-width: 992px) {
+ .container {
+ width: 970px;
+ }
+}
+@media (min-width: 1200px) {
+ .container {
+ width: 1170px;
+ }
+}
+.container-fluid {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto;
+}
+.row {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
+ position: relative;
+ min-height: 1px;
+ padding-right: 15px;
+ padding-left: 15px;
+}
+.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {
+ float: left;
+}
+.col-xs-12 {
+ width: 100%;
+}
+.col-xs-11 {
+ width: 91.66666667%;
+}
+.col-xs-10 {
+ width: 83.33333333%;
+}
+.col-xs-9 {
+ width: 75%;
+}
+.col-xs-8 {
+ width: 66.66666667%;
+}
+.col-xs-7 {
+ width: 58.33333333%;
+}
+.col-xs-6 {
+ width: 50%;
+}
+.col-xs-5 {
+ width: 41.66666667%;
+}
+.col-xs-4 {
+ width: 33.33333333%;
+}
+.col-xs-3 {
+ width: 25%;
+}
+.col-xs-2 {
+ width: 16.66666667%;
+}
+.col-xs-1 {
+ width: 8.33333333%;
+}
+.col-xs-pull-12 {
+ right: 100%;
+}
+.col-xs-pull-11 {
+ right: 91.66666667%;
+}
+.col-xs-pull-10 {
+ right: 83.33333333%;
+}
+.col-xs-pull-9 {
+ right: 75%;
+}
+.col-xs-pull-8 {
+ right: 66.66666667%;
+}
+.col-xs-pull-7 {
+ right: 58.33333333%;
+}
+.col-xs-pull-6 {
+ right: 50%;
+}
+.col-xs-pull-5 {
+ right: 41.66666667%;
+}
+.col-xs-pull-4 {
+ right: 33.33333333%;
+}
+.col-xs-pull-3 {
+ right: 25%;
+}
+.col-xs-pull-2 {
+ right: 16.66666667%;
+}
+.col-xs-pull-1 {
+ right: 8.33333333%;
+}
+.col-xs-pull-0 {
+ right: auto;
+}
+.col-xs-push-12 {
+ left: 100%;
+}
+.col-xs-push-11 {
+ left: 91.66666667%;
+}
+.col-xs-push-10 {
+ left: 83.33333333%;
+}
+.col-xs-push-9 {
+ left: 75%;
+}
+.col-xs-push-8 {
+ left: 66.66666667%;
+}
+.col-xs-push-7 {
+ left: 58.33333333%;
+}
+.col-xs-push-6 {
+ left: 50%;
+}
+.col-xs-push-5 {
+ left: 41.66666667%;
+}
+.col-xs-push-4 {
+ left: 33.33333333%;
+}
+.col-xs-push-3 {
+ left: 25%;
+}
+.col-xs-push-2 {
+ left: 16.66666667%;
+}
+.col-xs-push-1 {
+ left: 8.33333333%;
+}
+.col-xs-push-0 {
+ left: auto;
+}
+.col-xs-offset-12 {
+ margin-left: 100%;
+}
+.col-xs-offset-11 {
+ margin-left: 91.66666667%;
+}
+.col-xs-offset-10 {
+ margin-left: 83.33333333%;
+}
+.col-xs-offset-9 {
+ margin-left: 75%;
+}
+.col-xs-offset-8 {
+ margin-left: 66.66666667%;
+}
+.col-xs-offset-7 {
+ margin-left: 58.33333333%;
+}
+.col-xs-offset-6 {
+ margin-left: 50%;
+}
+.col-xs-offset-5 {
+ margin-left: 41.66666667%;
+}
+.col-xs-offset-4 {
+ margin-left: 33.33333333%;
+}
+.col-xs-offset-3 {
+ margin-left: 25%;
+}
+.col-xs-offset-2 {
+ margin-left: 16.66666667%;
+}
+.col-xs-offset-1 {
+ margin-left: 8.33333333%;
+}
+.col-xs-offset-0 {
+ margin-left: 0;
+}
+@media (min-width: 768px) {
+ .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
+ float: left;
+ }
+ .col-sm-12 {
+ width: 100%;
+ }
+ .col-sm-11 {
+ width: 91.66666667%;
+ }
+ .col-sm-10 {
+ width: 83.33333333%;
+ }
+ .col-sm-9 {
+ width: 75%;
+ }
+ .col-sm-8 {
+ width: 66.66666667%;
+ }
+ .col-sm-7 {
+ width: 58.33333333%;
+ }
+ .col-sm-6 {
+ width: 50%;
+ }
+ .col-sm-5 {
+ width: 41.66666667%;
+ }
+ .col-sm-4 {
+ width: 33.33333333%;
+ }
+ .col-sm-3 {
+ width: 25%;
+ }
+ .col-sm-2 {
+ width: 16.66666667%;
+ }
+ .col-sm-1 {
+ width: 8.33333333%;
+ }
+ .col-sm-pull-12 {
+ right: 100%;
+ }
+ .col-sm-pull-11 {
+ right: 91.66666667%;
+ }
+ .col-sm-pull-10 {
+ right: 83.33333333%;
+ }
+ .col-sm-pull-9 {
+ right: 75%;
+ }
+ .col-sm-pull-8 {
+ right: 66.66666667%;
+ }
+ .col-sm-pull-7 {
+ right: 58.33333333%;
+ }
+ .col-sm-pull-6 {
+ right: 50%;
+ }
+ .col-sm-pull-5 {
+ right: 41.66666667%;
+ }
+ .col-sm-pull-4 {
+ right: 33.33333333%;
+ }
+ .col-sm-pull-3 {
+ right: 25%;
+ }
+ .col-sm-pull-2 {
+ right: 16.66666667%;
+ }
+ .col-sm-pull-1 {
+ right: 8.33333333%;
+ }
+ .col-sm-pull-0 {
+ right: auto;
+ }
+ .col-sm-push-12 {
+ left: 100%;
+ }
+ .col-sm-push-11 {
+ left: 91.66666667%;
+ }
+ .col-sm-push-10 {
+ left: 83.33333333%;
+ }
+ .col-sm-push-9 {
+ left: 75%;
+ }
+ .col-sm-push-8 {
+ left: 66.66666667%;
+ }
+ .col-sm-push-7 {
+ left: 58.33333333%;
+ }
+ .col-sm-push-6 {
+ left: 50%;
+ }
+ .col-sm-push-5 {
+ left: 41.66666667%;
+ }
+ .col-sm-push-4 {
+ left: 33.33333333%;
+ }
+ .col-sm-push-3 {
+ left: 25%;
+ }
+ .col-sm-push-2 {
+ left: 16.66666667%;
+ }
+ .col-sm-push-1 {
+ left: 8.33333333%;
+ }
+ .col-sm-push-0 {
+ left: auto;
+ }
+ .col-sm-offset-12 {
+ margin-left: 100%;
+ }
+ .col-sm-offset-11 {
+ margin-left: 91.66666667%;
+ }
+ .col-sm-offset-10 {
+ margin-left: 83.33333333%;
+ }
+ .col-sm-offset-9 {
+ margin-left: 75%;
+ }
+ .col-sm-offset-8 {
+ margin-left: 66.66666667%;
+ }
+ .col-sm-offset-7 {
+ margin-left: 58.33333333%;
+ }
+ .col-sm-offset-6 {
+ margin-left: 50%;
+ }
+ .col-sm-offset-5 {
+ margin-left: 41.66666667%;
+ }
+ .col-sm-offset-4 {
+ margin-left: 33.33333333%;
+ }
+ .col-sm-offset-3 {
+ margin-left: 25%;
+ }
+ .col-sm-offset-2 {
+ margin-left: 16.66666667%;
+ }
+ .col-sm-offset-1 {
+ margin-left: 8.33333333%;
+ }
+ .col-sm-offset-0 {
+ margin-left: 0;
+ }
+}
+@media (min-width: 992px) {
+ .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
+ float: left;
+ }
+ .col-md-12 {
+ width: 100%;
+ }
+ .col-md-11 {
+ width: 91.66666667%;
+ }
+ .col-md-10 {
+ width: 83.33333333%;
+ }
+ .col-md-9 {
+ width: 75%;
+ }
+ .col-md-8 {
+ width: 66.66666667%;
+ }
+ .col-md-7 {
+ width: 58.33333333%;
+ }
+ .col-md-6 {
+ width: 50%;
+ }
+ .col-md-5 {
+ width: 41.66666667%;
+ }
+ .col-md-4 {
+ width: 33.33333333%;
+ }
+ .col-md-3 {
+ width: 25%;
+ }
+ .col-md-2 {
+ width: 16.66666667%;
+ }
+ .col-md-1 {
+ width: 8.33333333%;
+ }
+ .col-md-pull-12 {
+ right: 100%;
+ }
+ .col-md-pull-11 {
+ right: 91.66666667%;
+ }
+ .col-md-pull-10 {
+ right: 83.33333333%;
+ }
+ .col-md-pull-9 {
+ right: 75%;
+ }
+ .col-md-pull-8 {
+ right: 66.66666667%;
+ }
+ .col-md-pull-7 {
+ right: 58.33333333%;
+ }
+ .col-md-pull-6 {
+ right: 50%;
+ }
+ .col-md-pull-5 {
+ right: 41.66666667%;
+ }
+ .col-md-pull-4 {
+ right: 33.33333333%;
+ }
+ .col-md-pull-3 {
+ right: 25%;
+ }
+ .col-md-pull-2 {
+ right: 16.66666667%;
+ }
+ .col-md-pull-1 {
+ right: 8.33333333%;
+ }
+ .col-md-pull-0 {
+ right: auto;
+ }
+ .col-md-push-12 {
+ left: 100%;
+ }
+ .col-md-push-11 {
+ left: 91.66666667%;
+ }
+ .col-md-push-10 {
+ left: 83.33333333%;
+ }
+ .col-md-push-9 {
+ left: 75%;
+ }
+ .col-md-push-8 {
+ left: 66.66666667%;
+ }
+ .col-md-push-7 {
+ left: 58.33333333%;
+ }
+ .col-md-push-6 {
+ left: 50%;
+ }
+ .col-md-push-5 {
+ left: 41.66666667%;
+ }
+ .col-md-push-4 {
+ left: 33.33333333%;
+ }
+ .col-md-push-3 {
+ left: 25%;
+ }
+ .col-md-push-2 {
+ left: 16.66666667%;
+ }
+ .col-md-push-1 {
+ left: 8.33333333%;
+ }
+ .col-md-push-0 {
+ left: auto;
+ }
+ .col-md-offset-12 {
+ margin-left: 100%;
+ }
+ .col-md-offset-11 {
+ margin-left: 91.66666667%;
+ }
+ .col-md-offset-10 {
+ margin-left: 83.33333333%;
+ }
+ .col-md-offset-9 {
+ margin-left: 75%;
+ }
+ .col-md-offset-8 {
+ margin-left: 66.66666667%;
+ }
+ .col-md-offset-7 {
+ margin-left: 58.33333333%;
+ }
+ .col-md-offset-6 {
+ margin-left: 50%;
+ }
+ .col-md-offset-5 {
+ margin-left: 41.66666667%;
+ }
+ .col-md-offset-4 {
+ margin-left: 33.33333333%;
+ }
+ .col-md-offset-3 {
+ margin-left: 25%;
+ }
+ .col-md-offset-2 {
+ margin-left: 16.66666667%;
+ }
+ .col-md-offset-1 {
+ margin-left: 8.33333333%;
+ }
+ .col-md-offset-0 {
+ margin-left: 0;
+ }
+}
+@media (min-width: 1200px) {
+ .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
+ float: left;
+ }
+ .col-lg-12 {
+ width: 100%;
+ }
+ .col-lg-11 {
+ width: 91.66666667%;
+ }
+ .col-lg-10 {
+ width: 83.33333333%;
+ }
+ .col-lg-9 {
+ width: 75%;
+ }
+ .col-lg-8 {
+ width: 66.66666667%;
+ }
+ .col-lg-7 {
+ width: 58.33333333%;
+ }
+ .col-lg-6 {
+ width: 50%;
+ }
+ .col-lg-5 {
+ width: 41.66666667%;
+ }
+ .col-lg-4 {
+ width: 33.33333333%;
+ }
+ .col-lg-3 {
+ width: 25%;
+ }
+ .col-lg-2 {
+ width: 16.66666667%;
+ }
+ .col-lg-1 {
+ width: 8.33333333%;
+ }
+ .col-lg-pull-12 {
+ right: 100%;
+ }
+ .col-lg-pull-11 {
+ right: 91.66666667%;
+ }
+ .col-lg-pull-10 {
+ right: 83.33333333%;
+ }
+ .col-lg-pull-9 {
+ right: 75%;
+ }
+ .col-lg-pull-8 {
+ right: 66.66666667%;
+ }
+ .col-lg-pull-7 {
+ right: 58.33333333%;
+ }
+ .col-lg-pull-6 {
+ right: 50%;
+ }
+ .col-lg-pull-5 {
+ right: 41.66666667%;
+ }
+ .col-lg-pull-4 {
+ right: 33.33333333%;
+ }
+ .col-lg-pull-3 {
+ right: 25%;
+ }
+ .col-lg-pull-2 {
+ right: 16.66666667%;
+ }
+ .col-lg-pull-1 {
+ right: 8.33333333%;
+ }
+ .col-lg-pull-0 {
+ right: auto;
+ }
+ .col-lg-push-12 {
+ left: 100%;
+ }
+ .col-lg-push-11 {
+ left: 91.66666667%;
+ }
+ .col-lg-push-10 {
+ left: 83.33333333%;
+ }
+ .col-lg-push-9 {
+ left: 75%;
+ }
+ .col-lg-push-8 {
+ left: 66.66666667%;
+ }
+ .col-lg-push-7 {
+ left: 58.33333333%;
+ }
+ .col-lg-push-6 {
+ left: 50%;
+ }
+ .col-lg-push-5 {
+ left: 41.66666667%;
+ }
+ .col-lg-push-4 {
+ left: 33.33333333%;
+ }
+ .col-lg-push-3 {
+ left: 25%;
+ }
+ .col-lg-push-2 {
+ left: 16.66666667%;
+ }
+ .col-lg-push-1 {
+ left: 8.33333333%;
+ }
+ .col-lg-push-0 {
+ left: auto;
+ }
+ .col-lg-offset-12 {
+ margin-left: 100%;
+ }
+ .col-lg-offset-11 {
+ margin-left: 91.66666667%;
+ }
+ .col-lg-offset-10 {
+ margin-left: 83.33333333%;
+ }
+ .col-lg-offset-9 {
+ margin-left: 75%;
+ }
+ .col-lg-offset-8 {
+ margin-left: 66.66666667%;
+ }
+ .col-lg-offset-7 {
+ margin-left: 58.33333333%;
+ }
+ .col-lg-offset-6 {
+ margin-left: 50%;
+ }
+ .col-lg-offset-5 {
+ margin-left: 41.66666667%;
+ }
+ .col-lg-offset-4 {
+ margin-left: 33.33333333%;
+ }
+ .col-lg-offset-3 {
+ margin-left: 25%;
+ }
+ .col-lg-offset-2 {
+ margin-left: 16.66666667%;
+ }
+ .col-lg-offset-1 {
+ margin-left: 8.33333333%;
+ }
+ .col-lg-offset-0 {
+ margin-left: 0;
+ }
+}
+table {
+ background-color: transparent;
+}
+caption {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ color: #777;
+ text-align: left;
+}
+th {
+ text-align: left;
+}
+.table {
+ width: 100%;
+ max-width: 100%;
+ margin-bottom: 20px;
+}
+.table > thead > tr > th,
+.table > tbody > tr > th,
+.table > tfoot > tr > th,
+.table > thead > tr > td,
+.table > tbody > tr > td,
+.table > tfoot > tr > td {
+ padding: 8px;
+ line-height: 1.42857143;
+ vertical-align: top;
+ border-top: 1px solid #ddd;
+}
+.table > thead > tr > th {
+ vertical-align: bottom;
+ border-bottom: 2px solid #ddd;
+}
+.table > caption + thead > tr:first-child > th,
+.table > colgroup + thead > tr:first-child > th,
+.table > thead:first-child > tr:first-child > th,
+.table > caption + thead > tr:first-child > td,
+.table > colgroup + thead > tr:first-child > td,
+.table > thead:first-child > tr:first-child > td {
+ border-top: 0;
+}
+.table > tbody + tbody {
+ border-top: 2px solid #ddd;
+}
+.table .table {
+ background-color: #fff;
+}
+.table-condensed > thead > tr > th,
+.table-condensed > tbody > tr > th,
+.table-condensed > tfoot > tr > th,
+.table-condensed > thead > tr > td,
+.table-condensed > tbody > tr > td,
+.table-condensed > tfoot > tr > td {
+ padding: 5px;
+}
+.table-bordered {
+ border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+ border: 1px solid #ddd;
+}
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+ border-bottom-width: 2px;
+}
+.table-striped > tbody > tr:nth-of-type(odd) {
+ background-color: #f9f9f9;
+}
+.table-hover > tbody > tr:hover {
+ background-color: #f5f5f5;
+}
+table col[class*="col-"] {
+ position: static;
+ display: table-column;
+ float: none;
+}
+table td[class*="col-"],
+table th[class*="col-"] {
+ position: static;
+ display: table-cell;
+ float: none;
+}
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+ background-color: #f5f5f5;
+}
+.table-hover > tbody > tr > td.active:hover,
+.table-hover > tbody > tr > th.active:hover,
+.table-hover > tbody > tr.active:hover > td,
+.table-hover > tbody > tr:hover > .active,
+.table-hover > tbody > tr.active:hover > th {
+ background-color: #e8e8e8;
+}
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+ background-color: #dff0d8;
+}
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td,
+.table-hover > tbody > tr:hover > .success,
+.table-hover > tbody > tr.success:hover > th {
+ background-color: #d0e9c6;
+}
+.table > thead > tr > td.info,
+.table > tbody > tr > td.info,
+.table > tfoot > tr > td.info,
+.table > thead > tr > th.info,
+.table > tbody > tr > th.info,
+.table > tfoot > tr > th.info,
+.table > thead > tr.info > td,
+.table > tbody > tr.info > td,
+.table > tfoot > tr.info > td,
+.table > thead > tr.info > th,
+.table > tbody > tr.info > th,
+.table > tfoot > tr.info > th {
+ background-color: #d9edf7;
+}
+.table-hover > tbody > tr > td.info:hover,
+.table-hover > tbody > tr > th.info:hover,
+.table-hover > tbody > tr.info:hover > td,
+.table-hover > tbody > tr:hover > .info,
+.table-hover > tbody > tr.info:hover > th {
+ background-color: #c4e3f3;
+}
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+ background-color: #fcf8e3;
+}
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td,
+.table-hover > tbody > tr:hover > .warning,
+.table-hover > tbody > tr.warning:hover > th {
+ background-color: #faf2cc;
+}
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+ background-color: #f2dede;
+}
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td,
+.table-hover > tbody > tr:hover > .danger,
+.table-hover > tbody > tr.danger:hover > th {
+ background-color: #ebcccc;
+}
+.table-responsive {
+ min-height: .01%;
+ overflow-x: auto;
+}
+@media screen and (max-width: 767px) {
+ .table-responsive {
+ width: 100%;
+ margin-bottom: 15px;
+ overflow-y: hidden;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+ border: 1px solid #ddd;
+ }
+ .table-responsive > .table {
+ margin-bottom: 0;
+ }
+ .table-responsive > .table > thead > tr > th,
+ .table-responsive > .table > tbody > tr > th,
+ .table-responsive > .table > tfoot > tr > th,
+ .table-responsive > .table > thead > tr > td,
+ .table-responsive > .table > tbody > tr > td,
+ .table-responsive > .table > tfoot > tr > td {
+ white-space: nowrap;
+ }
+ .table-responsive > .table-bordered {
+ border: 0;
+ }
+ .table-responsive > .table-bordered > thead > tr > th:first-child,
+ .table-responsive > .table-bordered > tbody > tr > th:first-child,
+ .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+ .table-responsive > .table-bordered > thead > tr > td:first-child,
+ .table-responsive > .table-bordered > tbody > tr > td:first-child,
+ .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+ border-left: 0;
+ }
+ .table-responsive > .table-bordered > thead > tr > th:last-child,
+ .table-responsive > .table-bordered > tbody > tr > th:last-child,
+ .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+ .table-responsive > .table-bordered > thead > tr > td:last-child,
+ .table-responsive > .table-bordered > tbody > tr > td:last-child,
+ .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+ border-right: 0;
+ }
+ .table-responsive > .table-bordered > tbody > tr:last-child > th,
+ .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+ .table-responsive > .table-bordered > tbody > tr:last-child > td,
+ .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+ border-bottom: 0;
+ }
+}
+fieldset {
+ min-width: 0;
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: inherit;
+ color: #333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+label {
+ display: inline-block;
+ max-width: 100%;
+ margin-bottom: 5px;
+ font-weight: bold;
+}
+input[type="search"] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ margin-top: 1px \9;
+ line-height: normal;
+}
+input[type="file"] {
+ display: block;
+}
+input[type="range"] {
+ display: block;
+ width: 100%;
+}
+select[multiple],
+select[size] {
+ height: auto;
+}
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ outline: thin dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+output {
+ display: block;
+ padding-top: 7px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+}
+.form-control {
+ display: block;
+ width: 100%;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555;
+ background-color: #fff;
+ background-image: none;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
+ -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+}
+.form-control:focus {
+ border-color: #66afe9;
+ outline: 0;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
+}
+.form-control::-moz-placeholder {
+ color: #999;
+ opacity: 1;
+}
+.form-control:-ms-input-placeholder {
+ color: #999;
+}
+.form-control::-webkit-input-placeholder {
+ color: #999;
+}
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+ cursor: not-allowed;
+ background-color: #eee;
+ opacity: 1;
+}
+textarea.form-control {
+ height: auto;
+}
+input[type="search"] {
+ -webkit-appearance: none;
+}
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
+ input[type="date"],
+ input[type="time"],
+ input[type="datetime-local"],
+ input[type="month"] {
+ line-height: 34px;
+ }
+ input[type="date"].input-sm,
+ input[type="time"].input-sm,
+ input[type="datetime-local"].input-sm,
+ input[type="month"].input-sm,
+ .input-group-sm input[type="date"],
+ .input-group-sm input[type="time"],
+ .input-group-sm input[type="datetime-local"],
+ .input-group-sm input[type="month"] {
+ line-height: 30px;
+ }
+ input[type="date"].input-lg,
+ input[type="time"].input-lg,
+ input[type="datetime-local"].input-lg,
+ input[type="month"].input-lg,
+ .input-group-lg input[type="date"],
+ .input-group-lg input[type="time"],
+ .input-group-lg input[type="datetime-local"],
+ .input-group-lg input[type="month"] {
+ line-height: 46px;
+ }
+}
+.form-group {
+ margin-bottom: 15px;
+}
+.radio,
+.checkbox {
+ position: relative;
+ display: block;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+.radio label,
+.checkbox label {
+ min-height: 20px;
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: normal;
+ cursor: pointer;
+}
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+ position: absolute;
+ margin-top: 4px \9;
+ margin-left: -20px;
+}
+.radio + .radio,
+.checkbox + .checkbox {
+ margin-top: -5px;
+}
+.radio-inline,
+.checkbox-inline {
+ display: inline-block;
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: normal;
+ vertical-align: middle;
+ cursor: pointer;
+}
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+ margin-top: 0;
+ margin-left: 10px;
+}
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"].disabled,
+input[type="checkbox"].disabled,
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"] {
+ cursor: not-allowed;
+}
+.radio-inline.disabled,
+.checkbox-inline.disabled,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox-inline {
+ cursor: not-allowed;
+}
+.radio.disabled label,
+.checkbox.disabled label,
+fieldset[disabled] .radio label,
+fieldset[disabled] .checkbox label {
+ cursor: not-allowed;
+}
+.form-control-static {
+ padding-top: 7px;
+ padding-bottom: 7px;
+ margin-bottom: 0;
+}
+.form-control-static.input-lg,
+.form-control-static.input-sm {
+ padding-right: 0;
+ padding-left: 0;
+}
+.input-sm {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+select.input-sm {
+ height: 30px;
+ line-height: 30px;
+}
+textarea.input-sm,
+select[multiple].input-sm {
+ height: auto;
+}
+.form-group-sm .form-control {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+select.form-group-sm .form-control {
+ height: 30px;
+ line-height: 30px;
+}
+textarea.form-group-sm .form-control,
+select[multiple].form-group-sm .form-control {
+ height: auto;
+}
+.form-group-sm .form-control-static {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+}
+.input-lg {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+select.input-lg {
+ height: 46px;
+ line-height: 46px;
+}
+textarea.input-lg,
+select[multiple].input-lg {
+ height: auto;
+}
+.form-group-lg .form-control {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+select.form-group-lg .form-control {
+ height: 46px;
+ line-height: 46px;
+}
+textarea.form-group-lg .form-control,
+select[multiple].form-group-lg .form-control {
+ height: auto;
+}
+.form-group-lg .form-control-static {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+}
+.has-feedback {
+ position: relative;
+}
+.has-feedback .form-control {
+ padding-right: 42.5px;
+}
+.form-control-feedback {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 2;
+ display: block;
+ width: 34px;
+ height: 34px;
+ line-height: 34px;
+ text-align: center;
+ pointer-events: none;
+}
+.input-lg + .form-control-feedback {
+ width: 46px;
+ height: 46px;
+ line-height: 46px;
+}
+.input-sm + .form-control-feedback {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+}
+.has-success .help-block,
+.has-success .control-label,
+.has-success .radio,
+.has-success .checkbox,
+.has-success .radio-inline,
+.has-success .checkbox-inline,
+.has-success.radio label,
+.has-success.checkbox label,
+.has-success.radio-inline label,
+.has-success.checkbox-inline label {
+ color: #3c763d;
+}
+.has-success .form-control {
+ border-color: #3c763d;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-success .form-control:focus {
+ border-color: #2b542c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+}
+.has-success .input-group-addon {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #3c763d;
+}
+.has-success .form-control-feedback {
+ color: #3c763d;
+}
+.has-warning .help-block,
+.has-warning .control-label,
+.has-warning .radio,
+.has-warning .checkbox,
+.has-warning .radio-inline,
+.has-warning .checkbox-inline,
+.has-warning.radio label,
+.has-warning.checkbox label,
+.has-warning.radio-inline label,
+.has-warning.checkbox-inline label {
+ color: #8a6d3b;
+}
+.has-warning .form-control {
+ border-color: #8a6d3b;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-warning .form-control:focus {
+ border-color: #66512c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+}
+.has-warning .input-group-addon {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #8a6d3b;
+}
+.has-warning .form-control-feedback {
+ color: #8a6d3b;
+}
+.has-error .help-block,
+.has-error .control-label,
+.has-error .radio,
+.has-error .checkbox,
+.has-error .radio-inline,
+.has-error .checkbox-inline,
+.has-error.radio label,
+.has-error.checkbox label,
+.has-error.radio-inline label,
+.has-error.checkbox-inline label {
+ color: #a94442;
+}
+.has-error .form-control {
+ border-color: #a94442;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+}
+.has-error .form-control:focus {
+ border-color: #843534;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+}
+.has-error .input-group-addon {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #a94442;
+}
+.has-error .form-control-feedback {
+ color: #a94442;
+}
+.has-feedback label ~ .form-control-feedback {
+ top: 25px;
+}
+.has-feedback label.sr-only ~ .form-control-feedback {
+ top: 0;
+}
+.help-block {
+ display: block;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ color: #737373;
+}
+@media (min-width: 768px) {
+ .form-inline .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .form-inline .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle;
+ }
+ .form-inline .form-control-static {
+ display: inline-block;
+ }
+ .form-inline .input-group {
+ display: inline-table;
+ vertical-align: middle;
+ }
+ .form-inline .input-group .input-group-addon,
+ .form-inline .input-group .input-group-btn,
+ .form-inline .input-group .form-control {
+ width: auto;
+ }
+ .form-inline .input-group > .form-control {
+ width: 100%;
+ }
+ .form-inline .control-label {
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .form-inline .radio,
+ .form-inline .checkbox {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .form-inline .radio label,
+ .form-inline .checkbox label {
+ padding-left: 0;
+ }
+ .form-inline .radio input[type="radio"],
+ .form-inline .checkbox input[type="checkbox"] {
+ position: relative;
+ margin-left: 0;
+ }
+ .form-inline .has-feedback .form-control-feedback {
+ top: 0;
+ }
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+ padding-top: 7px;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+.form-horizontal .radio,
+.form-horizontal .checkbox {
+ min-height: 27px;
+}
+.form-horizontal .form-group {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+@media (min-width: 768px) {
+ .form-horizontal .control-label {
+ padding-top: 7px;
+ margin-bottom: 0;
+ text-align: right;
+ }
+}
+.form-horizontal .has-feedback .form-control-feedback {
+ right: 15px;
+}
+@media (min-width: 768px) {
+ .form-horizontal .form-group-lg .control-label {
+ padding-top: 14.333333px;
+ }
+}
+@media (min-width: 768px) {
+ .form-horizontal .form-group-sm .control-label {
+ padding-top: 6px;
+ }
+}
+.btn {
+ display: inline-block;
+ padding: 6px 12px;
+ margin-bottom: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1.42857143;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+.btn:focus,
+.btn:active:focus,
+.btn.active:focus,
+.btn.focus,
+.btn:active.focus,
+.btn.active.focus {
+ outline: thin dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+.btn:hover,
+.btn:focus,
+.btn.focus {
+ color: #333;
+ text-decoration: none;
+}
+.btn:active,
+.btn.active {
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+ pointer-events: none;
+ cursor: not-allowed;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ opacity: .65;
+}
+.btn-default {
+ color: #333;
+ background-color: #fff;
+ border-color: #ccc;
+}
+.btn-default:hover,
+.btn-default:focus,
+.btn-default.focus,
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+ color: #333;
+ background-color: #e6e6e6;
+ border-color: #adadad;
+}
+.btn-default:active,
+.btn-default.active,
+.open > .dropdown-toggle.btn-default {
+ background-image: none;
+}
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled.focus,
+.btn-default[disabled].focus,
+fieldset[disabled] .btn-default.focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+ background-color: #fff;
+ border-color: #ccc;
+}
+.btn-default .badge {
+ color: #fff;
+ background-color: #333;
+}
+.btn-primary {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #2e6da4;
+}
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary.focus,
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+ color: #fff;
+ background-color: #286090;
+ border-color: #204d74;
+}
+.btn-primary:active,
+.btn-primary.active,
+.open > .dropdown-toggle.btn-primary {
+ background-image: none;
+}
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled.focus,
+.btn-primary[disabled].focus,
+fieldset[disabled] .btn-primary.focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+ background-color: #337ab7;
+ border-color: #2e6da4;
+}
+.btn-primary .badge {
+ color: #337ab7;
+ background-color: #fff;
+}
+.btn-success {
+ color: #fff;
+ background-color: #5cb85c;
+ border-color: #4cae4c;
+}
+.btn-success:hover,
+.btn-success:focus,
+.btn-success.focus,
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+ color: #fff;
+ background-color: #449d44;
+ border-color: #398439;
+}
+.btn-success:active,
+.btn-success.active,
+.open > .dropdown-toggle.btn-success {
+ background-image: none;
+}
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled.focus,
+.btn-success[disabled].focus,
+fieldset[disabled] .btn-success.focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+ background-color: #5cb85c;
+ border-color: #4cae4c;
+}
+.btn-success .badge {
+ color: #5cb85c;
+ background-color: #fff;
+}
+.btn-info {
+ color: #fff;
+ background-color: #5bc0de;
+ border-color: #46b8da;
+}
+.btn-info:hover,
+.btn-info:focus,
+.btn-info.focus,
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+ color: #fff;
+ background-color: #31b0d5;
+ border-color: #269abc;
+}
+.btn-info:active,
+.btn-info.active,
+.open > .dropdown-toggle.btn-info {
+ background-image: none;
+}
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled.focus,
+.btn-info[disabled].focus,
+fieldset[disabled] .btn-info.focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+ background-color: #5bc0de;
+ border-color: #46b8da;
+}
+.btn-info .badge {
+ color: #5bc0de;
+ background-color: #fff;
+}
+.btn-warning {
+ color: #fff;
+ background-color: #f0ad4e;
+ border-color: #eea236;
+}
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning.focus,
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+ color: #fff;
+ background-color: #ec971f;
+ border-color: #d58512;
+}
+.btn-warning:active,
+.btn-warning.active,
+.open > .dropdown-toggle.btn-warning {
+ background-image: none;
+}
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled.focus,
+.btn-warning[disabled].focus,
+fieldset[disabled] .btn-warning.focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+ background-color: #f0ad4e;
+ border-color: #eea236;
+}
+.btn-warning .badge {
+ color: #f0ad4e;
+ background-color: #fff;
+}
+.btn-danger {
+ color: #fff;
+ background-color: #d9534f;
+ border-color: #d43f3a;
+}
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger.focus,
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+ color: #fff;
+ background-color: #c9302c;
+ border-color: #ac2925;
+}
+.btn-danger:active,
+.btn-danger.active,
+.open > .dropdown-toggle.btn-danger {
+ background-image: none;
+}
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled.focus,
+.btn-danger[disabled].focus,
+fieldset[disabled] .btn-danger.focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+ background-color: #d9534f;
+ border-color: #d43f3a;
+}
+.btn-danger .badge {
+ color: #d9534f;
+ background-color: #fff;
+}
+.btn-link {
+ font-weight: normal;
+ color: #337ab7;
+ border-radius: 0;
+}
+.btn-link,
+.btn-link:active,
+.btn-link.active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+ background-color: transparent;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+ border-color: transparent;
+}
+.btn-link:hover,
+.btn-link:focus {
+ color: #23527c;
+ text-decoration: underline;
+ background-color: transparent;
+}
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+ color: #777;
+ text-decoration: none;
+}
+.btn-lg,
+.btn-group-lg > .btn {
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+.btn-sm,
+.btn-group-sm > .btn {
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+.btn-xs,
+.btn-group-xs > .btn {
+ padding: 1px 5px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+.btn-block {
+ display: block;
+ width: 100%;
+}
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+ width: 100%;
+}
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity .15s linear;
+ -o-transition: opacity .15s linear;
+ transition: opacity .15s linear;
+}
+.fade.in {
+ opacity: 1;
+}
+.collapse {
+ display: none;
+ visibility: hidden;
+}
+.collapse.in {
+ display: block;
+ visibility: visible;
+}
+tr.collapse.in {
+ display: table-row;
+}
+tbody.collapse.in {
+ display: table-row-group;
+}
+.collapsing {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition-timing-function: ease;
+ -o-transition-timing-function: ease;
+ transition-timing-function: ease;
+ -webkit-transition-duration: .35s;
+ -o-transition-duration: .35s;
+ transition-duration: .35s;
+ -webkit-transition-property: height, visibility;
+ -o-transition-property: height, visibility;
+ transition-property: height, visibility;
+}
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 2px;
+ vertical-align: middle;
+ border-top: 4px solid;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+}
+.dropup,
+.dropdown {
+ position: relative;
+}
+.dropdown-toggle:focus {
+ outline: 0;
+}
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ font-size: 14px;
+ text-align: left;
+ list-style: none;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, .15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+}
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+.dropdown-menu .divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+.dropdown-menu > li > a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 1.42857143;
+ color: #333;
+ white-space: nowrap;
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+ color: #262626;
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+ color: #fff;
+ text-decoration: none;
+ background-color: #337ab7;
+ outline: 0;
+}
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ color: #777;
+}
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+ background-image: none;
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.open > .dropdown-menu {
+ display: block;
+}
+.open > a {
+ outline: 0;
+}
+.dropdown-menu-right {
+ right: 0;
+ left: auto;
+}
+.dropdown-menu-left {
+ right: auto;
+ left: 0;
+}
+.dropdown-header {
+ display: block;
+ padding: 3px 20px;
+ font-size: 12px;
+ line-height: 1.42857143;
+ color: #777;
+ white-space: nowrap;
+}
+.dropdown-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 990;
+}
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ content: "";
+ border-top: 0;
+ border-bottom: 4px solid;
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 2px;
+}
+@media (min-width: 768px) {
+ .navbar-right .dropdown-menu {
+ right: 0;
+ left: auto;
+ }
+ .navbar-right .dropdown-menu-left {
+ right: auto;
+ left: 0;
+ }
+}
+.btn-group,
+.btn-group-vertical {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+}
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+ position: relative;
+ float: left;
+}
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+ z-index: 2;
+}
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+ margin-left: -1px;
+}
+.btn-toolbar {
+ margin-left: -5px;
+}
+.btn-toolbar .btn-group,
+.btn-toolbar .input-group {
+ float: left;
+}
+.btn-toolbar > .btn,
+.btn-toolbar > .btn-group,
+.btn-toolbar > .input-group {
+ margin-left: 5px;
+}
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+ border-radius: 0;
+}
+.btn-group > .btn:first-child {
+ margin-left: 0;
+}
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group > .btn-group {
+ float: left;
+}
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0;
+}
+.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+.btn-group > .btn + .dropdown-toggle {
+ padding-right: 8px;
+ padding-left: 8px;
+}
+.btn-group > .btn-lg + .dropdown-toggle {
+ padding-right: 12px;
+ padding-left: 12px;
+}
+.btn-group.open .dropdown-toggle {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn-group.open .dropdown-toggle.btn-link {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+.btn .caret {
+ margin-left: 0;
+}
+.btn-lg .caret {
+ border-width: 5px 5px 0;
+ border-bottom-width: 0;
+}
+.dropup .btn-lg .caret {
+ border-width: 0 5px 5px;
+}
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group,
+.btn-group-vertical > .btn-group > .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ max-width: 100%;
+}
+.btn-group-vertical > .btn-group > .btn {
+ float: none;
+}
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+ margin-top: -1px;
+ margin-left: 0;
+}
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+ border-radius: 0;
+}
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 4px;
+}
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0;
+}
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+.btn-group-justified {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: separate;
+}
+.btn-group-justified > .btn,
+.btn-group-justified > .btn-group {
+ display: table-cell;
+ float: none;
+ width: 1%;
+}
+.btn-group-justified > .btn-group .btn {
+ width: 100%;
+}
+.btn-group-justified > .btn-group .dropdown-menu {
+ left: auto;
+}
+[data-toggle="buttons"] > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="radio"],
+[data-toggle="buttons"] > .btn input[type="checkbox"],
+[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ pointer-events: none;
+}
+.input-group {
+ position: relative;
+ display: table;
+ border-collapse: separate;
+}
+.input-group[class*="col-"] {
+ float: none;
+ padding-right: 0;
+ padding-left: 0;
+}
+.input-group .form-control {
+ position: relative;
+ z-index: 2;
+ float: left;
+ width: 100%;
+ margin-bottom: 0;
+}
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+ height: 46px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.3333333;
+ border-radius: 6px;
+}
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+ height: 46px;
+ line-height: 46px;
+}
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn,
+select[multiple].input-group-lg > .form-control,
+select[multiple].input-group-lg > .input-group-addon,
+select[multiple].input-group-lg > .input-group-btn > .btn {
+ height: auto;
+}
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ line-height: 30px;
+}
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn,
+select[multiple].input-group-sm > .form-control,
+select[multiple].input-group-sm > .input-group-addon,
+select[multiple].input-group-sm > .input-group-btn > .btn {
+ height: auto;
+}
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+ display: table-cell;
+}
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+ border-radius: 0;
+}
+.input-group-addon,
+.input-group-btn {
+ width: 1%;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+.input-group-addon {
+ padding: 6px 12px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1;
+ color: #555;
+ text-align: center;
+ background-color: #eee;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+.input-group-addon.input-sm {
+ padding: 5px 10px;
+ font-size: 12px;
+ border-radius: 3px;
+}
+.input-group-addon.input-lg {
+ padding: 10px 16px;
+ font-size: 18px;
+ border-radius: 6px;
+}
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+ margin-top: 0;
+}
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),
+.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.input-group-addon:first-child {
+ border-right: 0;
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child),
+.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.input-group-addon:last-child {
+ border-left: 0;
+}
+.input-group-btn {
+ position: relative;
+ font-size: 0;
+ white-space: nowrap;
+}
+.input-group-btn > .btn {
+ position: relative;
+}
+.input-group-btn > .btn + .btn {
+ margin-left: -1px;
+}
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:focus,
+.input-group-btn > .btn:active {
+ z-index: 2;
+}
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group {
+ margin-right: -1px;
+}
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group {
+ margin-left: -1px;
+}
+.nav {
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+.nav > li {
+ position: relative;
+ display: block;
+}
+.nav > li > a {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+}
+.nav > li > a:hover,
+.nav > li > a:focus {
+ text-decoration: none;
+ background-color: #eee;
+}
+.nav > li.disabled > a {
+ color: #777;
+}
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+ color: #777;
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+}
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+ background-color: #eee;
+ border-color: #337ab7;
+}
+.nav .nav-divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+.nav > li > a > img {
+ max-width: none;
+}
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+.nav-tabs > li {
+ float: left;
+ margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+ margin-right: 2px;
+ line-height: 1.42857143;
+ border: 1px solid transparent;
+ border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+ border-color: #eee #eee #ddd;
+}
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+ color: #555;
+ cursor: default;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+.nav-tabs.nav-justified {
+ width: 100%;
+ border-bottom: 0;
+}
+.nav-tabs.nav-justified > li {
+ float: none;
+}
+.nav-tabs.nav-justified > li > a {
+ margin-bottom: 5px;
+ text-align: center;
+}
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto;
+}
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+ .nav-tabs.nav-justified > li > a {
+ margin-bottom: 0;
+ }
+}
+.nav-tabs.nav-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px;
+}
+.nav-tabs.nav-justified > .active > a,
+.nav-tabs.nav-justified > .active > a:hover,
+.nav-tabs.nav-justified > .active > a:focus {
+ border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified > li > a {
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0;
+ }
+ .nav-tabs.nav-justified > .active > a,
+ .nav-tabs.nav-justified > .active > a:hover,
+ .nav-tabs.nav-justified > .active > a:focus {
+ border-bottom-color: #fff;
+ }
+}
+.nav-pills > li {
+ float: left;
+}
+.nav-pills > li > a {
+ border-radius: 4px;
+}
+.nav-pills > li + li {
+ margin-left: 2px;
+}
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+ color: #fff;
+ background-color: #337ab7;
+}
+.nav-stacked > li {
+ float: none;
+}
+.nav-stacked > li + li {
+ margin-top: 2px;
+ margin-left: 0;
+}
+.nav-justified {
+ width: 100%;
+}
+.nav-justified > li {
+ float: none;
+}
+.nav-justified > li > a {
+ margin-bottom: 5px;
+ text-align: center;
+}
+.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto;
+}
+@media (min-width: 768px) {
+ .nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+ .nav-justified > li > a {
+ margin-bottom: 0;
+ }
+}
+.nav-tabs-justified {
+ border-bottom: 0;
+}
+.nav-tabs-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px;
+}
+.nav-tabs-justified > .active > a,
+.nav-tabs-justified > .active > a:hover,
+.nav-tabs-justified > .active > a:focus {
+ border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+ .nav-tabs-justified > li > a {
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0;
+ }
+ .nav-tabs-justified > .active > a,
+ .nav-tabs-justified > .active > a:hover,
+ .nav-tabs-justified > .active > a:focus {
+ border-bottom-color: #fff;
+ }
+}
+.tab-content > .tab-pane {
+ display: none;
+ visibility: hidden;
+}
+.tab-content > .active {
+ display: block;
+ visibility: visible;
+}
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+.navbar {
+ position: relative;
+ min-height: 50px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+}
+@media (min-width: 768px) {
+ .navbar {
+ border-radius: 4px;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-header {
+ float: left;
+ }
+}
+.navbar-collapse {
+ padding-right: 15px;
+ padding-left: 15px;
+ overflow-x: visible;
+ -webkit-overflow-scrolling: touch;
+ border-top: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+}
+.navbar-collapse.in {
+ overflow-y: auto;
+}
+@media (min-width: 768px) {
+ .navbar-collapse {
+ width: auto;
+ border-top: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+ .navbar-collapse.collapse {
+ display: block !important;
+ height: auto !important;
+ padding-bottom: 0;
+ overflow: visible !important;
+ visibility: visible !important;
+ }
+ .navbar-collapse.in {
+ overflow-y: visible;
+ }
+ .navbar-fixed-top .navbar-collapse,
+ .navbar-static-top .navbar-collapse,
+ .navbar-fixed-bottom .navbar-collapse {
+ padding-right: 0;
+ padding-left: 0;
+ }
+}
+.navbar-fixed-top .navbar-collapse,
+.navbar-fixed-bottom .navbar-collapse {
+ max-height: 340px;
+}
+@media (max-device-width: 480px) and (orientation: landscape) {
+ .navbar-fixed-top .navbar-collapse,
+ .navbar-fixed-bottom .navbar-collapse {
+ max-height: 200px;
+ }
+}
+.container > .navbar-header,
+.container-fluid > .navbar-header,
+.container > .navbar-collapse,
+.container-fluid > .navbar-collapse {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+@media (min-width: 768px) {
+ .container > .navbar-header,
+ .container-fluid > .navbar-header,
+ .container > .navbar-collapse,
+ .container-fluid > .navbar-collapse {
+ margin-right: 0;
+ margin-left: 0;
+ }
+}
+.navbar-static-top {
+ z-index: 1000;
+ border-width: 0 0 1px;
+}
+@media (min-width: 768px) {
+ .navbar-static-top {
+ border-radius: 0;
+ }
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+}
+@media (min-width: 768px) {
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ border-radius: 0;
+ }
+}
+.navbar-fixed-top {
+ top: 0;
+ border-width: 0 0 1px;
+}
+.navbar-fixed-bottom {
+ bottom: 0;
+ margin-bottom: 0;
+ border-width: 1px 0 0;
+}
+.navbar-brand {
+ float: left;
+ height: 50px;
+ padding: 15px 15px;
+ font-size: 18px;
+ line-height: 20px;
+}
+.navbar-brand:hover,
+.navbar-brand:focus {
+ text-decoration: none;
+}
+.navbar-brand > img {
+ display: block;
+}
+@media (min-width: 768px) {
+ .navbar > .container .navbar-brand,
+ .navbar > .container-fluid .navbar-brand {
+ margin-left: -15px;
+ }
+}
+.navbar-toggle {
+ position: relative;
+ float: right;
+ padding: 9px 10px;
+ margin-top: 8px;
+ margin-right: 15px;
+ margin-bottom: 8px;
+ background-color: transparent;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+.navbar-toggle:focus {
+ outline: 0;
+}
+.navbar-toggle .icon-bar {
+ display: block;
+ width: 22px;
+ height: 2px;
+ border-radius: 1px;
+}
+.navbar-toggle .icon-bar + .icon-bar {
+ margin-top: 4px;
+}
+@media (min-width: 768px) {
+ .navbar-toggle {
+ display: none;
+ }
+}
+.navbar-nav {
+ margin: 7.5px -15px;
+}
+.navbar-nav > li > a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ line-height: 20px;
+}
+@media (max-width: 767px) {
+ .navbar-nav .open .dropdown-menu {
+ position: static;
+ float: none;
+ width: auto;
+ margin-top: 0;
+ background-color: transparent;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+ .navbar-nav .open .dropdown-menu > li > a,
+ .navbar-nav .open .dropdown-menu .dropdown-header {
+ padding: 5px 15px 5px 25px;
+ }
+ .navbar-nav .open .dropdown-menu > li > a {
+ line-height: 20px;
+ }
+ .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-nav .open .dropdown-menu > li > a:focus {
+ background-image: none;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-nav {
+ float: left;
+ margin: 0;
+ }
+ .navbar-nav > li {
+ float: left;
+ }
+ .navbar-nav > li > a {
+ padding-top: 15px;
+ padding-bottom: 15px;
+ }
+}
+.navbar-form {
+ padding: 10px 15px;
+ margin-top: 8px;
+ margin-right: -15px;
+ margin-bottom: 8px;
+ margin-left: -15px;
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+}
+@media (min-width: 768px) {
+ .navbar-form .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .navbar-form .form-control {
+ display: inline-block;
+ width: auto;
+ vertical-align: middle;
+ }
+ .navbar-form .form-control-static {
+ display: inline-block;
+ }
+ .navbar-form .input-group {
+ display: inline-table;
+ vertical-align: middle;
+ }
+ .navbar-form .input-group .input-group-addon,
+ .navbar-form .input-group .input-group-btn,
+ .navbar-form .input-group .form-control {
+ width: auto;
+ }
+ .navbar-form .input-group > .form-control {
+ width: 100%;
+ }
+ .navbar-form .control-label {
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .navbar-form .radio,
+ .navbar-form .checkbox {
+ display: inline-block;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .navbar-form .radio label,
+ .navbar-form .checkbox label {
+ padding-left: 0;
+ }
+ .navbar-form .radio input[type="radio"],
+ .navbar-form .checkbox input[type="checkbox"] {
+ position: relative;
+ margin-left: 0;
+ }
+ .navbar-form .has-feedback .form-control-feedback {
+ top: 0;
+ }
+}
+@media (max-width: 767px) {
+ .navbar-form .form-group {
+ margin-bottom: 5px;
+ }
+ .navbar-form .form-group:last-child {
+ margin-bottom: 0;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-form {
+ width: auto;
+ padding-top: 0;
+ padding-bottom: 0;
+ margin-right: 0;
+ margin-left: 0;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+}
+.navbar-nav > li > .dropdown-menu {
+ margin-top: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+ margin-bottom: 0;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.navbar-btn {
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+.navbar-btn.btn-sm {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+.navbar-btn.btn-xs {
+ margin-top: 14px;
+ margin-bottom: 14px;
+}
+.navbar-text {
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
+@media (min-width: 768px) {
+ .navbar-text {
+ float: left;
+ margin-right: 15px;
+ margin-left: 15px;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-left {
+ float: left !important;
+ }
+ .navbar-right {
+ float: right !important;
+ margin-right: -15px;
+ }
+ .navbar-right ~ .navbar-right {
+ margin-right: 0;
+ }
+}
+.navbar-default {
+ background-color: #f8f8f8;
+ border-color: #e7e7e7;
+}
+.navbar-default .navbar-brand {
+ color: #777;
+}
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+ color: #5e5e5e;
+ background-color: transparent;
+}
+.navbar-default .navbar-text {
+ color: #777;
+}
+.navbar-default .navbar-nav > li > a {
+ color: #777;
+}
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+ color: #333;
+ background-color: transparent;
+}
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+ color: #555;
+ background-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+ color: #ccc;
+ background-color: transparent;
+}
+.navbar-default .navbar-toggle {
+ border-color: #ddd;
+}
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+ background-color: #ddd;
+}
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: #888;
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+ border-color: #e7e7e7;
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+ color: #555;
+ background-color: #e7e7e7;
+}
+@media (max-width: 767px) {
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+ color: #777;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+ color: #333;
+ background-color: transparent;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #555;
+ background-color: #e7e7e7;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+ color: #ccc;
+ background-color: transparent;
+ }
+}
+.navbar-default .navbar-link {
+ color: #777;
+}
+.navbar-default .navbar-link:hover {
+ color: #333;
+}
+.navbar-default .btn-link {
+ color: #777;
+}
+.navbar-default .btn-link:hover,
+.navbar-default .btn-link:focus {
+ color: #333;
+}
+.navbar-default .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-default .btn-link:hover,
+.navbar-default .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-default .btn-link:focus {
+ color: #ccc;
+}
+.navbar-inverse {
+ background-color: #222;
+ border-color: #080808;
+}
+.navbar-inverse .navbar-brand {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+ color: #fff;
+ background-color: transparent;
+}
+.navbar-inverse .navbar-text {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+ color: #fff;
+ background-color: transparent;
+}
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+ color: #fff;
+ background-color: #080808;
+}
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+ color: #444;
+ background-color: transparent;
+}
+.navbar-inverse .navbar-toggle {
+ border-color: #333;
+}
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+ background-color: #333;
+}
+.navbar-inverse .navbar-toggle .icon-bar {
+ background-color: #fff;
+}
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+ border-color: #101010;
+}
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+ color: #fff;
+ background-color: #080808;
+}
+@media (max-width: 767px) {
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+ border-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu .divider {
+ background-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+ color: #9d9d9d;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+ color: #fff;
+ background-color: transparent;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #fff;
+ background-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+ color: #444;
+ background-color: transparent;
+ }
+}
+.navbar-inverse .navbar-link {
+ color: #9d9d9d;
+}
+.navbar-inverse .navbar-link:hover {
+ color: #fff;
+}
+.navbar-inverse .btn-link {
+ color: #9d9d9d;
+}
+.navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link:focus {
+ color: #fff;
+}
+.navbar-inverse .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-inverse .btn-link:hover,
+.navbar-inverse .btn-link[disabled]:focus,
+fieldset[disabled] .navbar-inverse .btn-link:focus {
+ color: #444;
+}
+.breadcrumb {
+ padding: 8px 15px;
+ margin-bottom: 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+}
+.breadcrumb > li {
+ display: inline-block;
+}
+.breadcrumb > li + li:before {
+ padding: 0 5px;
+ color: #ccc;
+ content: "/\00a0";
+}
+.breadcrumb > .active {
+ color: #777;
+}
+.pagination {
+ display: inline-block;
+ padding-left: 0;
+ margin: 20px 0;
+ border-radius: 4px;
+}
+.pagination > li {
+ display: inline;
+}
+.pagination > li > a,
+.pagination > li > span {
+ position: relative;
+ float: left;
+ padding: 6px 12px;
+ margin-left: -1px;
+ line-height: 1.42857143;
+ color: #337ab7;
+ text-decoration: none;
+ background-color: #fff;
+ border: 1px solid #ddd;
+}
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+ margin-left: 0;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+}
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+ color: #23527c;
+ background-color: #eee;
+ border-color: #ddd;
+}
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+ z-index: 2;
+ color: #fff;
+ cursor: default;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.pagination > .disabled > span,
+.pagination > .disabled > span:hover,
+.pagination > .disabled > span:focus,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #fff;
+ border-color: #ddd;
+}
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+ padding: 10px 16px;
+ font-size: 18px;
+}
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+}
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+ padding: 5px 10px;
+ font-size: 12px;
+}
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+.pager {
+ padding-left: 0;
+ margin: 20px 0;
+ text-align: center;
+ list-style: none;
+}
+.pager li {
+ display: inline;
+}
+.pager li > a,
+.pager li > span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 15px;
+}
+.pager li > a:hover,
+.pager li > a:focus {
+ text-decoration: none;
+ background-color: #eee;
+}
+.pager .next > a,
+.pager .next > span {
+ float: right;
+}
+.pager .previous > a,
+.pager .previous > span {
+ float: left;
+}
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #fff;
+}
+.label {
+ display: inline;
+ padding: .2em .6em .3em;
+ font-size: 75%;
+ font-weight: bold;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: .25em;
+}
+a.label:hover,
+a.label:focus {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer;
+}
+.label:empty {
+ display: none;
+}
+.btn .label {
+ position: relative;
+ top: -1px;
+}
+.label-default {
+ background-color: #777;
+}
+.label-default[href]:hover,
+.label-default[href]:focus {
+ background-color: #5e5e5e;
+}
+.label-primary {
+ background-color: #337ab7;
+}
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+ background-color: #286090;
+}
+.label-success {
+ background-color: #5cb85c;
+}
+.label-success[href]:hover,
+.label-success[href]:focus {
+ background-color: #449d44;
+}
+.label-info {
+ background-color: #5bc0de;
+}
+.label-info[href]:hover,
+.label-info[href]:focus {
+ background-color: #31b0d5;
+}
+.label-warning {
+ background-color: #f0ad4e;
+}
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+ background-color: #ec971f;
+}
+.label-danger {
+ background-color: #d9534f;
+}
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+ background-color: #c9302c;
+}
+.badge {
+ display: inline-block;
+ min-width: 10px;
+ padding: 3px 7px;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #777;
+ border-radius: 10px;
+}
+.badge:empty {
+ display: none;
+}
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+.btn-xs .badge {
+ top: 0;
+ padding: 1px 5px;
+}
+a.badge:hover,
+a.badge:focus {
+ color: #fff;
+ text-decoration: none;
+ cursor: pointer;
+}
+.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+ color: #337ab7;
+ background-color: #fff;
+}
+.list-group-item > .badge {
+ float: right;
+}
+.list-group-item > .badge + .badge {
+ margin-right: 5px;
+}
+.nav-pills > li > a > .badge {
+ margin-left: 3px;
+}
+.jumbotron {
+ padding: 30px 15px;
+ margin-bottom: 30px;
+ color: inherit;
+ background-color: #eee;
+}
+.jumbotron h1,
+.jumbotron .h1 {
+ color: inherit;
+}
+.jumbotron p {
+ margin-bottom: 15px;
+ font-size: 21px;
+ font-weight: 200;
+}
+.jumbotron > hr {
+ border-top-color: #d5d5d5;
+}
+.container .jumbotron,
+.container-fluid .jumbotron {
+ border-radius: 6px;
+}
+.jumbotron .container {
+ max-width: 100%;
+}
+@media screen and (min-width: 768px) {
+ .jumbotron {
+ padding: 48px 0;
+ }
+ .container .jumbotron,
+ .container-fluid .jumbotron {
+ padding-right: 60px;
+ padding-left: 60px;
+ }
+ .jumbotron h1,
+ .jumbotron .h1 {
+ font-size: 63px;
+ }
+}
+.thumbnail {
+ display: block;
+ padding: 4px;
+ margin-bottom: 20px;
+ line-height: 1.42857143;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ -webkit-transition: border .2s ease-in-out;
+ -o-transition: border .2s ease-in-out;
+ transition: border .2s ease-in-out;
+}
+.thumbnail > img,
+.thumbnail a > img {
+ margin-right: auto;
+ margin-left: auto;
+}
+a.thumbnail:hover,
+a.thumbnail:focus,
+a.thumbnail.active {
+ border-color: #337ab7;
+}
+.thumbnail .caption {
+ padding: 9px;
+ color: #333;
+}
+.alert {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+.alert h4 {
+ margin-top: 0;
+ color: inherit;
+}
+.alert .alert-link {
+ font-weight: bold;
+}
+.alert > p,
+.alert > ul {
+ margin-bottom: 0;
+}
+.alert > p + p {
+ margin-top: 5px;
+}
+.alert-dismissable,
+.alert-dismissible {
+ padding-right: 35px;
+}
+.alert-dismissable .close,
+.alert-dismissible .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ color: inherit;
+}
+.alert-success {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+.alert-success hr {
+ border-top-color: #c9e2b3;
+}
+.alert-success .alert-link {
+ color: #2b542c;
+}
+.alert-info {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+.alert-info hr {
+ border-top-color: #a6e1ec;
+}
+.alert-info .alert-link {
+ color: #245269;
+}
+.alert-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+}
+.alert-warning hr {
+ border-top-color: #f7e1b5;
+}
+.alert-warning .alert-link {
+ color: #66512c;
+}
+.alert-danger {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1;
+}
+.alert-danger hr {
+ border-top-color: #e4b9c0;
+}
+.alert-danger .alert-link {
+ color: #843534;
+}
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+}
+.progress-bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ line-height: 20px;
+ color: #fff;
+ text-align: center;
+ background-color: #337ab7;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+ -webkit-transition: width .6s ease;
+ -o-transition: width .6s ease;
+ transition: width .6s ease;
+}
+.progress-striped .progress-bar,
+.progress-bar-striped {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ -webkit-background-size: 40px 40px;
+ background-size: 40px 40px;
+}
+.progress.active .progress-bar,
+.progress-bar.active {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+.progress-bar-success {
+ background-color: #5cb85c;
+}
+.progress-striped .progress-bar-success {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-info {
+ background-color: #5bc0de;
+}
+.progress-striped .progress-bar-info {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-warning {
+ background-color: #f0ad4e;
+}
+.progress-striped .progress-bar-warning {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.progress-bar-danger {
+ background-color: #d9534f;
+}
+.progress-striped .progress-bar-danger {
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+}
+.media {
+ margin-top: 15px;
+}
+.media:first-child {
+ margin-top: 0;
+}
+.media,
+.media-body {
+ overflow: hidden;
+ zoom: 1;
+}
+.media-body {
+ width: 10000px;
+}
+.media-object {
+ display: block;
+}
+.media-right,
+.media > .pull-right {
+ padding-left: 10px;
+}
+.media-left,
+.media > .pull-left {
+ padding-right: 10px;
+}
+.media-left,
+.media-right,
+.media-body {
+ display: table-cell;
+ vertical-align: top;
+}
+.media-middle {
+ vertical-align: middle;
+}
+.media-bottom {
+ vertical-align: bottom;
+}
+.media-heading {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.media-list {
+ padding-left: 0;
+ list-style: none;
+}
+.list-group {
+ padding-left: 0;
+ margin-bottom: 20px;
+}
+.list-group-item {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+ margin-bottom: -1px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+}
+.list-group-item:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+.list-group-item:last-child {
+ margin-bottom: 0;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+a.list-group-item {
+ color: #555;
+}
+a.list-group-item .list-group-item-heading {
+ color: #333;
+}
+a.list-group-item:hover,
+a.list-group-item:focus {
+ color: #555;
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+.list-group-item.disabled,
+.list-group-item.disabled:hover,
+.list-group-item.disabled:focus {
+ color: #777;
+ cursor: not-allowed;
+ background-color: #eee;
+}
+.list-group-item.disabled .list-group-item-heading,
+.list-group-item.disabled:hover .list-group-item-heading,
+.list-group-item.disabled:focus .list-group-item-heading {
+ color: inherit;
+}
+.list-group-item.disabled .list-group-item-text,
+.list-group-item.disabled:hover .list-group-item-text,
+.list-group-item.disabled:focus .list-group-item-text {
+ color: #777;
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+ z-index: 2;
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading,
+.list-group-item.active .list-group-item-heading > small,
+.list-group-item.active:hover .list-group-item-heading > small,
+.list-group-item.active:focus .list-group-item-heading > small,
+.list-group-item.active .list-group-item-heading > .small,
+.list-group-item.active:hover .list-group-item-heading > .small,
+.list-group-item.active:focus .list-group-item-heading > .small {
+ color: inherit;
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+ color: #c7ddef;
+}
+.list-group-item-success {
+ color: #3c763d;
+ background-color: #dff0d8;
+}
+a.list-group-item-success {
+ color: #3c763d;
+}
+a.list-group-item-success .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-success:hover,
+a.list-group-item-success:focus {
+ color: #3c763d;
+ background-color: #d0e9c6;
+}
+a.list-group-item-success.active,
+a.list-group-item-success.active:hover,
+a.list-group-item-success.active:focus {
+ color: #fff;
+ background-color: #3c763d;
+ border-color: #3c763d;
+}
+.list-group-item-info {
+ color: #31708f;
+ background-color: #d9edf7;
+}
+a.list-group-item-info {
+ color: #31708f;
+}
+a.list-group-item-info .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-info:hover,
+a.list-group-item-info:focus {
+ color: #31708f;
+ background-color: #c4e3f3;
+}
+a.list-group-item-info.active,
+a.list-group-item-info.active:hover,
+a.list-group-item-info.active:focus {
+ color: #fff;
+ background-color: #31708f;
+ border-color: #31708f;
+}
+.list-group-item-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+}
+a.list-group-item-warning {
+ color: #8a6d3b;
+}
+a.list-group-item-warning .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-warning:hover,
+a.list-group-item-warning:focus {
+ color: #8a6d3b;
+ background-color: #faf2cc;
+}
+a.list-group-item-warning.active,
+a.list-group-item-warning.active:hover,
+a.list-group-item-warning.active:focus {
+ color: #fff;
+ background-color: #8a6d3b;
+ border-color: #8a6d3b;
+}
+.list-group-item-danger {
+ color: #a94442;
+ background-color: #f2dede;
+}
+a.list-group-item-danger {
+ color: #a94442;
+}
+a.list-group-item-danger .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-danger:hover,
+a.list-group-item-danger:focus {
+ color: #a94442;
+ background-color: #ebcccc;
+}
+a.list-group-item-danger.active,
+a.list-group-item-danger.active:hover,
+a.list-group-item-danger.active:focus {
+ color: #fff;
+ background-color: #a94442;
+ border-color: #a94442;
+}
+.list-group-item-heading {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.list-group-item-text {
+ margin-bottom: 0;
+ line-height: 1.3;
+}
+.panel {
+ margin-bottom: 20px;
+ background-color: #fff;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+}
+.panel-body {
+ padding: 15px;
+}
+.panel-heading {
+ padding: 10px 15px;
+ border-bottom: 1px solid transparent;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel-heading > .dropdown .dropdown-toggle {
+ color: inherit;
+}
+.panel-title {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-size: 16px;
+ color: inherit;
+}
+.panel-title > a,
+.panel-title > small,
+.panel-title > .small,
+.panel-title > small > a,
+.panel-title > .small > a {
+ color: inherit;
+}
+.panel-footer {
+ padding: 10px 15px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .list-group,
+.panel > .panel-collapse > .list-group {
+ margin-bottom: 0;
+}
+.panel > .list-group .list-group-item,
+.panel > .panel-collapse > .list-group .list-group-item {
+ border-width: 1px 0;
+ border-radius: 0;
+}
+.panel > .list-group:first-child .list-group-item:first-child,
+.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
+ border-top: 0;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel > .list-group:last-child .list-group-item:last-child,
+.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
+ border-bottom: 0;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel-heading + .list-group .list-group-item:first-child {
+ border-top-width: 0;
+}
+.list-group + .panel-footer {
+ border-top-width: 0;
+}
+.panel > .table,
+.panel > .table-responsive > .table,
+.panel > .panel-collapse > .table {
+ margin-bottom: 0;
+}
+.panel > .table caption,
+.panel > .table-responsive > .table caption,
+.panel > .panel-collapse > .table caption {
+ padding-right: 15px;
+ padding-left: 15px;
+}
+.panel > .table:first-child,
+.panel > .table-responsive:first-child > .table:first-child {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {
+ border-top-left-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {
+ border-top-right-radius: 3px;
+}
+.panel > .table:last-child,
+.panel > .table-responsive:last-child > .table:last-child {
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
+ border-bottom-right-radius: 3px;
+}
+.panel > .panel-body + .table,
+.panel > .panel-body + .table-responsive,
+.panel > .table + .panel-body,
+.panel > .table-responsive + .panel-body {
+ border-top: 1px solid #ddd;
+}
+.panel > .table > tbody:first-child > tr:first-child th,
+.panel > .table > tbody:first-child > tr:first-child td {
+ border-top: 0;
+}
+.panel > .table-bordered,
+.panel > .table-responsive > .table-bordered {
+ border: 0;
+}
+.panel > .table-bordered > thead > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
+.panel > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-bordered > thead > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
+.panel > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-bordered > tfoot > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+ border-left: 0;
+}
+.panel > .table-bordered > thead > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
+.panel > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-bordered > thead > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
+.panel > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-bordered > tfoot > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+ border-right: 0;
+}
+.panel > .table-bordered > thead > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,
+.panel > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-bordered > thead > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,
+.panel > .table-bordered > tbody > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {
+ border-bottom: 0;
+}
+.panel > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {
+ border-bottom: 0;
+}
+.panel > .table-responsive {
+ margin-bottom: 0;
+ border: 0;
+}
+.panel-group {
+ margin-bottom: 20px;
+}
+.panel-group .panel {
+ margin-bottom: 0;
+ border-radius: 4px;
+}
+.panel-group .panel + .panel {
+ margin-top: 5px;
+}
+.panel-group .panel-heading {
+ border-bottom: 0;
+}
+.panel-group .panel-heading + .panel-collapse > .panel-body,
+.panel-group .panel-heading + .panel-collapse > .list-group {
+ border-top: 1px solid #ddd;
+}
+.panel-group .panel-footer {
+ border-top: 0;
+}
+.panel-group .panel-footer + .panel-collapse .panel-body {
+ border-bottom: 1px solid #ddd;
+}
+.panel-default {
+ border-color: #ddd;
+}
+.panel-default > .panel-heading {
+ color: #333;
+ background-color: #f5f5f5;
+ border-color: #ddd;
+}
+.panel-default > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #ddd;
+}
+.panel-default > .panel-heading .badge {
+ color: #f5f5f5;
+ background-color: #333;
+}
+.panel-default > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #ddd;
+}
+.panel-primary {
+ border-color: #337ab7;
+}
+.panel-primary > .panel-heading {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.panel-primary > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #337ab7;
+}
+.panel-primary > .panel-heading .badge {
+ color: #337ab7;
+ background-color: #fff;
+}
+.panel-primary > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #337ab7;
+}
+.panel-success {
+ border-color: #d6e9c6;
+}
+.panel-success > .panel-heading {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+.panel-success > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #d6e9c6;
+}
+.panel-success > .panel-heading .badge {
+ color: #dff0d8;
+ background-color: #3c763d;
+}
+.panel-success > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #d6e9c6;
+}
+.panel-info {
+ border-color: #bce8f1;
+}
+.panel-info > .panel-heading {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+.panel-info > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #bce8f1;
+}
+.panel-info > .panel-heading .badge {
+ color: #d9edf7;
+ background-color: #31708f;
+}
+.panel-info > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #bce8f1;
+}
+.panel-warning {
+ border-color: #faebcc;
+}
+.panel-warning > .panel-heading {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+}
+.panel-warning > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #faebcc;
+}
+.panel-warning > .panel-heading .badge {
+ color: #fcf8e3;
+ background-color: #8a6d3b;
+}
+.panel-warning > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #faebcc;
+}
+.panel-danger {
+ border-color: #ebccd1;
+}
+.panel-danger > .panel-heading {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1;
+}
+.panel-danger > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #ebccd1;
+}
+.panel-danger > .panel-heading .badge {
+ color: #f2dede;
+ background-color: #a94442;
+}
+.panel-danger > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #ebccd1;
+}
+.embed-responsive {
+ position: relative;
+ display: block;
+ height: 0;
+ padding: 0;
+ overflow: hidden;
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive iframe,
+.embed-responsive embed,
+.embed-responsive object,
+.embed-responsive video {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 0;
+}
+.embed-responsive.embed-responsive-16by9 {
+ padding-bottom: 56.25%;
+}
+.embed-responsive.embed-responsive-4by3 {
+ padding-bottom: 75%;
+}
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+}
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, .15);
+}
+.well-lg {
+ padding: 24px;
+ border-radius: 6px;
+}
+.well-sm {
+ padding: 9px;
+ border-radius: 3px;
+}
+.close {
+ float: right;
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 1;
+ color: #000;
+ text-shadow: 0 1px 0 #fff;
+ filter: alpha(opacity=20);
+ opacity: .2;
+}
+.close:hover,
+.close:focus {
+ color: #000;
+ text-decoration: none;
+ cursor: pointer;
+ filter: alpha(opacity=50);
+ opacity: .5;
+}
+button.close {
+ -webkit-appearance: none;
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+}
+.modal-open {
+ overflow: hidden;
+}
+.modal {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ display: none;
+ overflow: hidden;
+ -webkit-overflow-scrolling: touch;
+ outline: 0;
+}
+.modal.fade .modal-dialog {
+ -webkit-transition: -webkit-transform .3s ease-out;
+ -o-transition: -o-transform .3s ease-out;
+ transition: transform .3s ease-out;
+ -webkit-transform: translate(0, -25%);
+ -ms-transform: translate(0, -25%);
+ -o-transform: translate(0, -25%);
+ transform: translate(0, -25%);
+}
+.modal.in .modal-dialog {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ -o-transform: translate(0, 0);
+ transform: translate(0, 0);
+}
+.modal-open .modal {
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+.modal-dialog {
+ position: relative;
+ width: auto;
+ margin: 10px;
+}
+.modal-content {
+ position: relative;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, .2);
+ border-radius: 6px;
+ outline: 0;
+ -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+ box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+}
+.modal-backdrop {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ background-color: #000;
+}
+.modal-backdrop.fade {
+ filter: alpha(opacity=0);
+ opacity: 0;
+}
+.modal-backdrop.in {
+ filter: alpha(opacity=50);
+ opacity: .5;
+}
+.modal-header {
+ min-height: 16.42857143px;
+ padding: 15px;
+ border-bottom: 1px solid #e5e5e5;
+}
+.modal-header .close {
+ margin-top: -2px;
+}
+.modal-title {
+ margin: 0;
+ line-height: 1.42857143;
+}
+.modal-body {
+ position: relative;
+ padding: 15px;
+}
+.modal-footer {
+ padding: 15px;
+ text-align: right;
+ border-top: 1px solid #e5e5e5;
+}
+.modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px;
+}
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+.modal-footer .btn-block + .btn-block {
+ margin-left: 0;
+}
+.modal-scrollbar-measure {
+ position: absolute;
+ top: -9999px;
+ width: 50px;
+ height: 50px;
+ overflow: scroll;
+}
+@media (min-width: 768px) {
+ .modal-dialog {
+ width: 600px;
+ margin: 30px auto;
+ }
+ .modal-content {
+ -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+ }
+ .modal-sm {
+ width: 300px;
+ }
+}
+@media (min-width: 992px) {
+ .modal-lg {
+ width: 900px;
+ }
+}
+.tooltip {
+ position: absolute;
+ z-index: 1070;
+ display: block;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 12px;
+ font-weight: normal;
+ line-height: 1.4;
+ visibility: visible;
+ filter: alpha(opacity=0);
+ opacity: 0;
+}
+.tooltip.in {
+ filter: alpha(opacity=90);
+ opacity: .9;
+}
+.tooltip.top {
+ padding: 5px 0;
+ margin-top: -3px;
+}
+.tooltip.right {
+ padding: 0 5px;
+ margin-left: 3px;
+}
+.tooltip.bottom {
+ padding: 5px 0;
+ margin-top: 3px;
+}
+.tooltip.left {
+ padding: 0 5px;
+ margin-left: -3px;
+}
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #fff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000;
+ border-radius: 4px;
+}
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000;
+}
+.tooltip.top-left .tooltip-arrow {
+ right: 5px;
+ bottom: 0;
+ margin-bottom: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000;
+}
+.tooltip.top-right .tooltip-arrow {
+ bottom: 0;
+ left: 5px;
+ margin-bottom: -5px;
+ border-width: 5px 5px 0;
+ border-top-color: #000;
+}
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-width: 5px 5px 5px 0;
+ border-right-color: #000;
+}
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-width: 5px 0 5px 5px;
+ border-left-color: #000;
+}
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000;
+}
+.tooltip.bottom-left .tooltip-arrow {
+ top: 0;
+ right: 5px;
+ margin-top: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000;
+}
+.tooltip.bottom-right .tooltip-arrow {
+ top: 0;
+ left: 5px;
+ margin-top: -5px;
+ border-width: 0 5px 5px;
+ border-bottom-color: #000;
+}
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1060;
+ display: none;
+ max-width: 276px;
+ padding: 1px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1.42857143;
+ text-align: left;
+ white-space: normal;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, .2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+}
+.popover.top {
+ margin-top: -10px;
+}
+.popover.right {
+ margin-left: 10px;
+}
+.popover.bottom {
+ margin-top: 10px;
+}
+.popover.left {
+ margin-left: -10px;
+}
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ border-radius: 5px 5px 0 0;
+}
+.popover-content {
+ padding: 9px 14px;
+}
+.popover > .arrow,
+.popover > .arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.popover > .arrow {
+ border-width: 11px;
+}
+.popover > .arrow:after {
+ content: "";
+ border-width: 10px;
+}
+.popover.top > .arrow {
+ bottom: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-color: #999;
+ border-top-color: rgba(0, 0, 0, .25);
+ border-bottom-width: 0;
+}
+.popover.top > .arrow:after {
+ bottom: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-color: #fff;
+ border-bottom-width: 0;
+}
+.popover.right > .arrow {
+ top: 50%;
+ left: -11px;
+ margin-top: -11px;
+ border-right-color: #999;
+ border-right-color: rgba(0, 0, 0, .25);
+ border-left-width: 0;
+}
+.popover.right > .arrow:after {
+ bottom: -10px;
+ left: 1px;
+ content: " ";
+ border-right-color: #fff;
+ border-left-width: 0;
+}
+.popover.bottom > .arrow {
+ top: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-width: 0;
+ border-bottom-color: #999;
+ border-bottom-color: rgba(0, 0, 0, .25);
+}
+.popover.bottom > .arrow:after {
+ top: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-width: 0;
+ border-bottom-color: #fff;
+}
+.popover.left > .arrow {
+ top: 50%;
+ right: -11px;
+ margin-top: -11px;
+ border-right-width: 0;
+ border-left-color: #999;
+ border-left-color: rgba(0, 0, 0, .25);
+}
+.popover.left > .arrow:after {
+ right: 1px;
+ bottom: -10px;
+ content: " ";
+ border-right-width: 0;
+ border-left-color: #fff;
+}
+.carousel {
+ position: relative;
+}
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+.carousel-inner > .item {
+ position: relative;
+ display: none;
+ -webkit-transition: .6s ease-in-out left;
+ -o-transition: .6s ease-in-out left;
+ transition: .6s ease-in-out left;
+}
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+ line-height: 1;
+}
+@media all and (transform-3d), (-webkit-transform-3d) {
+ .carousel-inner > .item {
+ -webkit-transition: -webkit-transform .6s ease-in-out;
+ -o-transition: -o-transform .6s ease-in-out;
+ transition: transform .6s ease-in-out;
+
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-perspective: 1000;
+ perspective: 1000;
+ }
+ .carousel-inner > .item.next,
+ .carousel-inner > .item.active.right {
+ left: 0;
+ -webkit-transform: translate3d(100%, 0, 0);
+ transform: translate3d(100%, 0, 0);
+ }
+ .carousel-inner > .item.prev,
+ .carousel-inner > .item.active.left {
+ left: 0;
+ -webkit-transform: translate3d(-100%, 0, 0);
+ transform: translate3d(-100%, 0, 0);
+ }
+ .carousel-inner > .item.next.left,
+ .carousel-inner > .item.prev.right,
+ .carousel-inner > .item.active {
+ left: 0;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ }
+}
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ display: block;
+}
+.carousel-inner > .active {
+ left: 0;
+}
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+.carousel-inner > .next {
+ left: 100%;
+}
+.carousel-inner > .prev {
+ left: -100%;
+}
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+ left: 0;
+}
+.carousel-inner > .active.left {
+ left: -100%;
+}
+.carousel-inner > .active.right {
+ left: 100%;
+}
+.carousel-control {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 15%;
+ font-size: 20px;
+ color: #fff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+ filter: alpha(opacity=50);
+ opacity: .5;
+}
+.carousel-control.left {
+ background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+ background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+ background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));
+ background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+ background-repeat: repeat-x;
+}
+.carousel-control.right {
+ right: 0;
+ left: auto;
+ background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+ background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+ background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));
+ background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+ background-repeat: repeat-x;
+}
+.carousel-control:hover,
+.carousel-control:focus {
+ color: #fff;
+ text-decoration: none;
+ filter: alpha(opacity=90);
+ outline: 0;
+ opacity: .9;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+ position: absolute;
+ top: 50%;
+ z-index: 5;
+ display: inline-block;
+}
+.carousel-control .icon-prev,
+.carousel-control .glyphicon-chevron-left {
+ left: 50%;
+ margin-left: -10px;
+}
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-right {
+ right: 50%;
+ margin-right: -10px;
+}
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+ width: 20px;
+ height: 20px;
+ margin-top: -10px;
+ font-family: serif;
+ line-height: 1;
+}
+.carousel-control .icon-prev:before {
+ content: '\2039';
+}
+.carousel-control .icon-next:before {
+ content: '\203a';
+}
+.carousel-indicators {
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ z-index: 15;
+ width: 60%;
+ padding-left: 0;
+ margin-left: -30%;
+ text-align: center;
+ list-style: none;
+}
+.carousel-indicators li {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ margin: 1px;
+ text-indent: -999px;
+ cursor: pointer;
+ background-color: #000 \9;
+ background-color: rgba(0, 0, 0, 0);
+ border: 1px solid #fff;
+ border-radius: 10px;
+}
+.carousel-indicators .active {
+ width: 12px;
+ height: 12px;
+ margin: 0;
+ background-color: #fff;
+}
+.carousel-caption {
+ position: absolute;
+ right: 15%;
+ bottom: 20px;
+ left: 15%;
+ z-index: 10;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #fff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
+}
+.carousel-caption .btn {
+ text-shadow: none;
+}
+@media screen and (min-width: 768px) {
+ .carousel-control .glyphicon-chevron-left,
+ .carousel-control .glyphicon-chevron-right,
+ .carousel-control .icon-prev,
+ .carousel-control .icon-next {
+ width: 30px;
+ height: 30px;
+ margin-top: -15px;
+ font-size: 30px;
+ }
+ .carousel-control .glyphicon-chevron-left,
+ .carousel-control .icon-prev {
+ margin-left: -15px;
+ }
+ .carousel-control .glyphicon-chevron-right,
+ .carousel-control .icon-next {
+ margin-right: -15px;
+ }
+ .carousel-caption {
+ right: 20%;
+ left: 20%;
+ padding-bottom: 30px;
+ }
+ .carousel-indicators {
+ bottom: 20px;
+ }
+}
+.clearfix:before,
+.clearfix:after,
+.dl-horizontal dd:before,
+.dl-horizontal dd:after,
+.container:before,
+.container:after,
+.container-fluid:before,
+.container-fluid:after,
+.row:before,
+.row:after,
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after,
+.btn-toolbar:before,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after,
+.nav:before,
+.nav:after,
+.navbar:before,
+.navbar:after,
+.navbar-header:before,
+.navbar-header:after,
+.navbar-collapse:before,
+.navbar-collapse:after,
+.pager:before,
+.pager:after,
+.panel-body:before,
+.panel-body:after,
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ content: " ";
+}
+.clearfix:after,
+.dl-horizontal dd:after,
+.container:after,
+.container-fluid:after,
+.row:after,
+.form-horizontal .form-group:after,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:after,
+.nav:after,
+.navbar:after,
+.navbar-header:after,
+.navbar-collapse:after,
+.pager:after,
+.panel-body:after,
+.modal-footer:after {
+ clear: both;
+}
+.center-block {
+ display: block;
+ margin-right: auto;
+ margin-left: auto;
+}
+.pull-right {
+ float: right !important;
+}
+.pull-left {
+ float: left !important;
+}
+.hide {
+ display: none !important;
+}
+.show {
+ display: block !important;
+}
+.invisible {
+ visibility: hidden;
+}
+.text-hide {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+.hidden {
+ display: none !important;
+ visibility: hidden !important;
+}
+.affix {
+ position: fixed;
+}
+@-ms-viewport {
+ width: device-width;
+}
+.visible-xs,
+.visible-sm,
+.visible-md,
+.visible-lg {
+ display: none !important;
+}
+.visible-xs-block,
+.visible-xs-inline,
+.visible-xs-inline-block,
+.visible-sm-block,
+.visible-sm-inline,
+.visible-sm-inline-block,
+.visible-md-block,
+.visible-md-inline,
+.visible-md-inline-block,
+.visible-lg-block,
+.visible-lg-inline,
+.visible-lg-inline-block {
+ display: none !important;
+}
+@media (max-width: 767px) {
+ .visible-xs {
+ display: block !important;
+ }
+ table.visible-xs {
+ display: table;
+ }
+ tr.visible-xs {
+ display: table-row !important;
+ }
+ th.visible-xs,
+ td.visible-xs {
+ display: table-cell !important;
+ }
+}
+@media (max-width: 767px) {
+ .visible-xs-block {
+ display: block !important;
+ }
+}
+@media (max-width: 767px) {
+ .visible-xs-inline {
+ display: inline !important;
+ }
+}
+@media (max-width: 767px) {
+ .visible-xs-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm {
+ display: block !important;
+ }
+ table.visible-sm {
+ display: table;
+ }
+ tr.visible-sm {
+ display: table-row !important;
+ }
+ th.visible-sm,
+ td.visible-sm {
+ display: table-cell !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm-block {
+ display: block !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm-inline {
+ display: inline !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md {
+ display: block !important;
+ }
+ table.visible-md {
+ display: table;
+ }
+ tr.visible-md {
+ display: table-row !important;
+ }
+ th.visible-md,
+ td.visible-md {
+ display: table-cell !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md-block {
+ display: block !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md-inline {
+ display: inline !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg {
+ display: block !important;
+ }
+ table.visible-lg {
+ display: table;
+ }
+ tr.visible-lg {
+ display: table-row !important;
+ }
+ th.visible-lg,
+ td.visible-lg {
+ display: table-cell !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg-block {
+ display: block !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg-inline {
+ display: inline !important;
+ }
+}
+@media (min-width: 1200px) {
+ .visible-lg-inline-block {
+ display: inline-block !important;
+ }
+}
+@media (max-width: 767px) {
+ .hidden-xs {
+ display: none !important;
+ }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+ .hidden-sm {
+ display: none !important;
+ }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+ .hidden-md {
+ display: none !important;
+ }
+}
+@media (min-width: 1200px) {
+ .hidden-lg {
+ display: none !important;
+ }
+}
+.visible-print {
+ display: none !important;
+}
+@media print {
+ .visible-print {
+ display: block !important;
+ }
+ table.visible-print {
+ display: table;
+ }
+ tr.visible-print {
+ display: table-row !important;
+ }
+ th.visible-print,
+ td.visible-print {
+ display: table-cell !important;
+ }
+}
+.visible-print-block {
+ display: none !important;
+}
+@media print {
+ .visible-print-block {
+ display: block !important;
+ }
+}
+.visible-print-inline {
+ display: none !important;
+}
+@media print {
+ .visible-print-inline {
+ display: inline !important;
+ }
+}
+.visible-print-inline-block {
+ display: none !important;
+}
+@media print {
+ .visible-print-inline-block {
+ display: inline-block !important;
+ }
+}
+@media print {
+ .hidden-print {
+ display: none !important;
+ }
+}
+/*# sourceMappingURL=bootstrap.css.map */
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map
new file mode 100644
index 000000000..ff579ff56
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"AAAA,6DAA4D;ACQ5D;EACE,yBAAA;EACA,4BAAA;EACA,gCAAA;EDND;ACaD;EACE,WAAA;EDXD;ACwBD;;;;;;;;;;;;;EAaE,gBAAA;EDtBD;AC8BD;;;;EAIE,uBAAA;EACA,0BAAA;ED5BD;ACoCD;EACE,eAAA;EACA,WAAA;EDlCD;AC0CD;;EAEE,eAAA;EDxCD;ACkDD;EACE,+BAAA;EDhDD;ACuDD;;EAEE,YAAA;EDrDD;AC+DD;EACE,2BAAA;ED7DD;ACoED;;EAEE,mBAAA;EDlED;ACyED;EACE,oBAAA;EDvED;AC+ED;EACE,gBAAA;EACA,kBAAA;ED7ED;ACoFD;EACE,kBAAA;EACA,aAAA;EDlFD;ACyFD;EACE,gBAAA;EDvFD;AC8FD;;EAEE,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,0BAAA;ED5FD;AC+FD;EACE,aAAA;ED7FD;ACgGD;EACE,iBAAA;ED9FD;ACwGD;EACE,WAAA;EDtGD;AC6GD;EACE,kBAAA;ED3GD;ACqHD;EACE,kBAAA;EDnHD;AC0HD;EACE,8BAAA;EACA,iCAAA;UAAA,yBAAA;EACA,WAAA;EDxHD;AC+HD;EACE,gBAAA;ED7HD;ACoID;;;;EAIE,mCAAA;EACA,gBAAA;EDlID;ACoJD;;;;;EAKE,gBAAA;EACA,eAAA;EACA,WAAA;EDlJD;ACyJD;EACE,mBAAA;EDvJD;ACiKD;;EAEE,sBAAA;ED/JD;AC0KD;;;;EAIE,4BAAA;EACA,iBAAA;EDxKD;AC+KD;;EAEE,iBAAA;ED7KD;ACoLD;;EAEE,WAAA;EACA,YAAA;EDlLD;AC0LD;EACE,qBAAA;EDxLD;ACmMD;;EAEE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,YAAA;EDjMD;AC0MD;;EAEE,cAAA;EDxMD;ACiND;EACE,+BAAA;EACA,8BAAA;EACA,iCAAA;EACA,yBAAA;ED/MD;ACwND;;EAEE,0BAAA;EDtND;AC6ND;EACE,2BAAA;EACA,eAAA;EACA,gCAAA;ED3ND;ACmOD;EACE,WAAA;EACA,YAAA;EDjOD;ACwOD;EACE,gBAAA;EDtOD;AC8OD;EACE,mBAAA;ED5OD;ACsPD;EACE,2BAAA;EACA,mBAAA;EDpPD;ACuPD;;EAEE,YAAA;EDrPD;AACD,sFAAqF;AE1ErF;EAnGI;;;IAGI,oCAAA;IACA,wBAAA;IACA,qCAAA;YAAA,6BAAA;IACA,8BAAA;IFgLL;EE7KC;;IAEI,4BAAA;IF+KL;EE5KC;IACI,8BAAA;IF8KL;EE3KC;IACI,+BAAA;IF6KL;EExKC;;IAEI,aAAA;IF0KL;EEvKC;;IAEI,wBAAA;IACA,0BAAA;IFyKL;EEtKC;IACI,6BAAA;IFwKL;EErKC;;IAEI,0BAAA;IFuKL;EEpKC;IACI,4BAAA;IFsKL;EEnKC;;;IAGI,YAAA;IACA,WAAA;IFqKL;EElKC;;IAEI,yBAAA;IFoKL;EE7JC;IACI,6BAAA;IF+JL;EE3JC;IACI,eAAA;IF6JL;EE3JC;;IAGQ,mCAAA;IF4JT;EEzJC;IACI,wBAAA;IF2JL;EExJC;IACI,sCAAA;IF0JL;EE3JC;;IAKQ,mCAAA;IF0JT;EEvJC;;IAGQ,mCAAA;IFwJT;EACF;AGpPD;EACE,qCAAA;EACA,uDAAA;EACA,iYAAA;EHsPD;AG9OD;EACE,oBAAA;EACA,UAAA;EACA,uBAAA;EACA,qCAAA;EACA,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,qCAAA;EACA,oCAAA;EHgPD;AG5OmC;EAAW,gBAAA;EH+O9C;AG9OmC;EAAW,gBAAA;EHiP9C;AG/OmC;;EAAW,kBAAA;EHmP9C;AGlPmC;EAAW,kBAAA;EHqP9C;AGpPmC;EAAW,kBAAA;EHuP9C;AGtPmC;EAAW,kBAAA;EHyP9C;AGxPmC;EAAW,kBAAA;EH2P9C;AG1PmC;EAAW,kBAAA;EH6P9C;AG5PmC;EAAW,kBAAA;EH+P9C;AG9PmC;EAAW,kBAAA;EHiQ9C;AGhQmC;EAAW,kBAAA;EHmQ9C;AGlQmC;EAAW,kBAAA;EHqQ9C;AGpQmC;EAAW,kBAAA;EHuQ9C;AGtQmC;EAAW,kBAAA;EHyQ9C;AGxQmC;EAAW,kBAAA;EH2Q9C;AG1QmC;EAAW,kBAAA;EH6Q9C;AG5QmC;EAAW,kBAAA;EH+Q9C;AG9QmC;EAAW,kBAAA;EHiR9C;AGhRmC;EAAW,kBAAA;EHmR9C;AGlRmC;EAAW,kBAAA;EHqR9C;AGpRmC;EAAW,kBAAA;EHuR9C;AGtRmC;EAAW,kBAAA;EHyR9C;AGxRmC;EAAW,kBAAA;EH2R9C;AG1RmC;EAAW,kBAAA;EH6R9C;AG5RmC;EAAW,kBAAA;EH+R9C;AG9RmC;EAAW,kBAAA;EHiS9C;AGhSmC;EAAW,kBAAA;EHmS9C;AGlSmC;EAAW,kBAAA;EHqS9C;AGpSmC;EAAW,kBAAA;EHuS9C;AGtSmC;EAAW,kBAAA;EHyS9C;AGxSmC;EAAW,kBAAA;EH2S9C;AG1SmC;EAAW,kBAAA;EH6S9C;AG5SmC;EAAW,kBAAA;EH+S9C;AG9SmC;EAAW,kBAAA;EHiT9C;AGhTmC;EAAW,kBAAA;EHmT9C;AGlTmC;EAAW,kBAAA;EHqT9C;AGpTmC;EAAW,kBAAA;EHuT9C;AGtTmC;EAAW,kBAAA;EHyT9C;AGxTmC;EAAW,kBAAA;EH2T9C;AG1TmC;EAAW,kBAAA;EH6T9C;AG5TmC;EAAW,kBAAA;EH+T9C;AG9TmC;EAAW,kBAAA;EHiU9C;AGhUmC;EAAW,kBAAA;EHmU9C;AGlUmC;EAAW,kBAAA;EHqU9C;AGpUmC;EAAW,kBAAA;EHuU9C;AGtUmC;EAAW,kBAAA;EHyU9C;AGxUmC;EAAW,kBAAA;EH2U9C;AG1UmC;EAAW,kBAAA;EH6U9C;AG5UmC;EAAW,kBAAA;EH+U9C;AG9UmC;EAAW,kBAAA;EHiV9C;AGhVmC;EAAW,kBAAA;EHmV9C;AGlVmC;EAAW,kBAAA;EHqV9C;AGpVmC;EAAW,kBAAA;EHuV9C;AGtVmC;EAAW,kBAAA;EHyV9C;AGxVmC;EAAW,kBAAA;EH2V9C;AG1VmC;EAAW,kBAAA;EH6V9C;AG5VmC;EAAW,kBAAA;EH+V9C;AG9VmC;EAAW,kBAAA;EHiW9C;AGhWmC;EAAW,kBAAA;EHmW9C;AGlWmC;EAAW,kBAAA;EHqW9C;AGpWmC;EAAW,kBAAA;EHuW9C;AGtWmC;EAAW,kBAAA;EHyW9C;AGxWmC;EAAW,kBAAA;EH2W9C;AG1WmC;EAAW,kBAAA;EH6W9C;AG5WmC;EAAW,kBAAA;EH+W9C;AG9WmC;EAAW,kBAAA;EHiX9C;AGhXmC;EAAW,kBAAA;EHmX9C;AGlXmC;EAAW,kBAAA;EHqX9C;AGpXmC;EAAW,kBAAA;EHuX9C;AGtXmC;EAAW,kBAAA;EHyX9C;AGxXmC;EAAW,kBAAA;EH2X9C;AG1XmC;EAAW,kBAAA;EH6X9C;AG5XmC;EAAW,kBAAA;EH+X9C;AG9XmC;EAAW,kBAAA;EHiY9C;AGhYmC;EAAW,kBAAA;EHmY9C;AGlYmC;EAAW,kBAAA;EHqY9C;AGpYmC;EAAW,kBAAA;EHuY9C;AGtYmC;EAAW,kBAAA;EHyY9C;AGxYmC;EAAW,kBAAA;EH2Y9C;AG1YmC;EAAW,kBAAA;EH6Y9C;AG5YmC;EAAW,kBAAA;EH+Y9C;AG9YmC;EAAW,kBAAA;EHiZ9C;AGhZmC;EAAW,kBAAA;EHmZ9C;AGlZmC;EAAW,kBAAA;EHqZ9C;AGpZmC;EAAW,kBAAA;EHuZ9C;AGtZmC;EAAW,kBAAA;EHyZ9C;AGxZmC;EAAW,kBAAA;EH2Z9C;AG1ZmC;EAAW,kBAAA;EH6Z9C;AG5ZmC;EAAW,kBAAA;EH+Z9C;AG9ZmC;EAAW,kBAAA;EHia9C;AGhamC;EAAW,kBAAA;EHma9C;AGlamC;EAAW,kBAAA;EHqa9C;AGpamC;EAAW,kBAAA;EHua9C;AGtamC;EAAW,kBAAA;EHya9C;AGxamC;EAAW,kBAAA;EH2a9C;AG1amC;EAAW,kBAAA;EH6a9C;AG5amC;EAAW,kBAAA;EH+a9C;AG9amC;EAAW,kBAAA;EHib9C;AGhbmC;EAAW,kBAAA;EHmb9C;AGlbmC;EAAW,kBAAA;EHqb9C;AGpbmC;EAAW,kBAAA;EHub9C;AGtbmC;EAAW,kBAAA;EHyb9C;AGxbmC;EAAW,kBAAA;EH2b9C;AG1bmC;EAAW,kBAAA;EH6b9C;AG5bmC;EAAW,kBAAA;EH+b9C;AG9bmC;EAAW,kBAAA;EHic9C;AGhcmC;EAAW,kBAAA;EHmc9C;AGlcmC;EAAW,kBAAA;EHqc9C;AGpcmC;EAAW,kBAAA;EHuc9C;AGtcmC;EAAW,kBAAA;EHyc9C;AGxcmC;EAAW,kBAAA;EH2c9C;AG1cmC;EAAW,kBAAA;EH6c9C;AG5cmC;EAAW,kBAAA;EH+c9C;AG9cmC;EAAW,kBAAA;EHid9C;AGhdmC;EAAW,kBAAA;EHmd9C;AGldmC;EAAW,kBAAA;EHqd9C;AGpdmC;EAAW,kBAAA;EHud9C;AGtdmC;EAAW,kBAAA;EHyd9C;AGxdmC;EAAW,kBAAA;EH2d9C;AG1dmC;EAAW,kBAAA;EH6d9C;AG5dmC;EAAW,kBAAA;EH+d9C;AG9dmC;EAAW,kBAAA;EHie9C;AGhemC;EAAW,kBAAA;EHme9C;AGlemC;EAAW,kBAAA;EHqe9C;AGpemC;EAAW,kBAAA;EHue9C;AGtemC;EAAW,kBAAA;EHye9C;AGxemC;EAAW,kBAAA;EH2e9C;AG1emC;EAAW,kBAAA;EH6e9C;AG5emC;EAAW,kBAAA;EH+e9C;AG9emC;EAAW,kBAAA;EHif9C;AGhfmC;EAAW,kBAAA;EHmf9C;AGlfmC;EAAW,kBAAA;EHqf9C;AGpfmC;EAAW,kBAAA;EHuf9C;AGtfmC;EAAW,kBAAA;EHyf9C;AGxfmC;EAAW,kBAAA;EH2f9C;AG1fmC;EAAW,kBAAA;EH6f9C;AG5fmC;EAAW,kBAAA;EH+f9C;AG9fmC;EAAW,kBAAA;EHigB9C;AGhgBmC;EAAW,kBAAA;EHmgB9C;AGlgBmC;EAAW,kBAAA;EHqgB9C;AGpgBmC;EAAW,kBAAA;EHugB9C;AGtgBmC;EAAW,kBAAA;EHygB9C;AGxgBmC;EAAW,kBAAA;EH2gB9C;AG1gBmC;EAAW,kBAAA;EH6gB9C;AG5gBmC;EAAW,kBAAA;EH+gB9C;AG9gBmC;EAAW,kBAAA;EHihB9C;AGhhBmC;EAAW,kBAAA;EHmhB9C;AGlhBmC;EAAW,kBAAA;EHqhB9C;AGphBmC;EAAW,kBAAA;EHuhB9C;AGthBmC;EAAW,kBAAA;EHyhB9C;AGxhBmC;EAAW,kBAAA;EH2hB9C;AG1hBmC;EAAW,kBAAA;EH6hB9C;AG5hBmC;EAAW,kBAAA;EH+hB9C;AG9hBmC;EAAW,kBAAA;EHiiB9C;AGhiBmC;EAAW,kBAAA;EHmiB9C;AGliBmC;EAAW,kBAAA;EHqiB9C;AGpiBmC;EAAW,kBAAA;EHuiB9C;AGtiBmC;EAAW,kBAAA;EHyiB9C;AGxiBmC;EAAW,kBAAA;EH2iB9C;AG1iBmC;EAAW,kBAAA;EH6iB9C;AG5iBmC;EAAW,kBAAA;EH+iB9C;AG9iBmC;EAAW,kBAAA;EHijB9C;AGhjBmC;EAAW,kBAAA;EHmjB9C;AGljBmC;EAAW,kBAAA;EHqjB9C;AGpjBmC;EAAW,kBAAA;EHujB9C;AGtjBmC;EAAW,kBAAA;EHyjB9C;AGxjBmC;EAAW,kBAAA;EH2jB9C;AG1jBmC;EAAW,kBAAA;EH6jB9C;AG5jBmC;EAAW,kBAAA;EH+jB9C;AG9jBmC;EAAW,kBAAA;EHikB9C;AGhkBmC;EAAW,kBAAA;EHmkB9C;AGlkBmC;EAAW,kBAAA;EHqkB9C;AGpkBmC;EAAW,kBAAA;EHukB9C;AGtkBmC;EAAW,kBAAA;EHykB9C;AGxkBmC;EAAW,kBAAA;EH2kB9C;AG1kBmC;EAAW,kBAAA;EH6kB9C;AG5kBmC;EAAW,kBAAA;EH+kB9C;AG9kBmC;EAAW,kBAAA;EHilB9C;AGhlBmC;EAAW,kBAAA;EHmlB9C;AGllBmC;EAAW,kBAAA;EHqlB9C;AGplBmC;EAAW,kBAAA;EHulB9C;AGtlBmC;EAAW,kBAAA;EHylB9C;AGxlBmC;EAAW,kBAAA;EH2lB9C;AG1lBmC;EAAW,kBAAA;EH6lB9C;AG5lBmC;EAAW,kBAAA;EH+lB9C;AG9lBmC;EAAW,kBAAA;EHimB9C;AGhmBmC;EAAW,kBAAA;EHmmB9C;AGlmBmC;EAAW,kBAAA;EHqmB9C;AGpmBmC;EAAW,kBAAA;EHumB9C;AGtmBmC;EAAW,kBAAA;EHymB9C;AGxmBmC;EAAW,kBAAA;EH2mB9C;AG1mBmC;EAAW,kBAAA;EH6mB9C;AG5mBmC;EAAW,kBAAA;EH+mB9C;AG9mBmC;EAAW,kBAAA;EHinB9C;AGhnBmC;EAAW,kBAAA;EHmnB9C;AGlnBmC;EAAW,kBAAA;EHqnB9C;AGpnBmC;EAAW,kBAAA;EHunB9C;AGtnBmC;EAAW,kBAAA;EHynB9C;AGxnBmC;EAAW,kBAAA;EH2nB9C;AG1nBmC;EAAW,kBAAA;EH6nB9C;AG5nBmC;EAAW,kBAAA;EH+nB9C;AG9nBmC;EAAW,kBAAA;EHioB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGxoBmC;EAAW,kBAAA;EH2oB9C;AG1oBmC;EAAW,kBAAA;EH6oB9C;AG5oBmC;EAAW,kBAAA;EH+oB9C;AG9oBmC;EAAW,kBAAA;EHipB9C;AGhpBmC;EAAW,kBAAA;EHmpB9C;AGlpBmC;EAAW,kBAAA;EHqpB9C;AGppBmC;EAAW,kBAAA;EHupB9C;AGtpBmC;EAAW,kBAAA;EHypB9C;AGxpBmC;EAAW,kBAAA;EH2pB9C;AG1pBmC;EAAW,kBAAA;EH6pB9C;AG5pBmC;EAAW,kBAAA;EH+pB9C;AG9pBmC;EAAW,kBAAA;EHiqB9C;AGhqBmC;EAAW,kBAAA;EHmqB9C;AGlqBmC;EAAW,kBAAA;EHqqB9C;AGpqBmC;EAAW,kBAAA;EHuqB9C;AGtqBmC;EAAW,kBAAA;EHyqB9C;AGxqBmC;EAAW,kBAAA;EH2qB9C;AG1qBmC;EAAW,kBAAA;EH6qB9C;AG5qBmC;EAAW,kBAAA;EH+qB9C;AG9qBmC;EAAW,kBAAA;EHirB9C;AGhrBmC;EAAW,kBAAA;EHmrB9C;AGlrBmC;EAAW,kBAAA;EHqrB9C;AGprBmC;EAAW,kBAAA;EHurB9C;AGtrBmC;EAAW,kBAAA;EHyrB9C;AGxrBmC;EAAW,kBAAA;EH2rB9C;AG1rBmC;EAAW,kBAAA;EH6rB9C;AG5rBmC;EAAW,kBAAA;EH+rB9C;AG9rBmC;EAAW,kBAAA;EHisB9C;AGhsBmC;EAAW,kBAAA;EHmsB9C;AGlsBmC;EAAW,kBAAA;EHqsB9C;AGpsBmC;EAAW,kBAAA;EHusB9C;AGtsBmC;EAAW,kBAAA;EHysB9C;AGxsBmC;EAAW,kBAAA;EH2sB9C;AG1sBmC;EAAW,kBAAA;EH6sB9C;AG5sBmC;EAAW,kBAAA;EH+sB9C;AG9sBmC;EAAW,kBAAA;EHitB9C;AGhtBmC;EAAW,kBAAA;EHmtB9C;AGltBmC;EAAW,kBAAA;EHqtB9C;AGptBmC;EAAW,kBAAA;EHutB9C;AGttBmC;EAAW,kBAAA;EHytB9C;AGxtBmC;EAAW,kBAAA;EH2tB9C;AG1tBmC;EAAW,kBAAA;EH6tB9C;AG5tBmC;EAAW,kBAAA;EH+tB9C;AG9tBmC;EAAW,kBAAA;EHiuB9C;AGhuBmC;EAAW,kBAAA;EHmuB9C;AGluBmC;EAAW,kBAAA;EHquB9C;AGpuBmC;EAAW,kBAAA;EHuuB9C;AGtuBmC;EAAW,kBAAA;EHyuB9C;AI3gCD;ECgEE,gCAAA;EACG,6BAAA;EACK,wBAAA;EL88BT;AI7gCD;;EC6DE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELo9BT;AI3gCD;EACE,iBAAA;EACA,+CAAA;EJ6gCD;AI1gCD;EACE,6DAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EJ4gCD;AIxgCD;;;;EAIE,sBAAA;EACA,oBAAA;EACA,sBAAA;EJ0gCD;AIpgCD;EACE,gBAAA;EACA,uBAAA;EJsgCD;AIpgCC;;EAEE,gBAAA;EACA,4BAAA;EJsgCH;AIngCC;EErDA,sBAAA;EAEA,4CAAA;EACA,sBAAA;EN0jCD;AI7/BD;EACE,WAAA;EJ+/BD;AIz/BD;EACE,wBAAA;EJ2/BD;AIv/BD;;;;;EGvEE,gBAAA;EACA,iBAAA;EACA,cAAA;EPqkCD;AI3/BD;EACE,oBAAA;EJ6/BD;AIv/BD;EACE,cAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EC6FA,0CAAA;EACK,qCAAA;EACG,kCAAA;EEvLR,uBAAA;EACA,iBAAA;EACA,cAAA;EPqlCD;AIv/BD;EACE,oBAAA;EJy/BD;AIn/BD;EACE,kBAAA;EACA,qBAAA;EACA,WAAA;EACA,+BAAA;EJq/BD;AI7+BD;EACE,oBAAA;EACA,YAAA;EACA,aAAA;EACA,cAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,WAAA;EJ++BD;AIv+BC;;EAEE,kBAAA;EACA,aAAA;EACA,cAAA;EACA,WAAA;EACA,mBAAA;EACA,YAAA;EJy+BH;AQpnCD;;;;;;;;;;;;EAEE,sBAAA;EACA,kBAAA;EACA,kBAAA;EACA,gBAAA;ERgoCD;AQroCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,qBAAA;EACA,gBAAA;EACA,gBAAA;ERspCH;AQlpCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERupCD;AQ3pCD;;;;;;;;;;;;EAQI,gBAAA;ERiqCH;AQ9pCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERmqCD;AQvqCD;;;;;;;;;;;;EAQI,gBAAA;ER6qCH;AQzqCD;;EAAU,iBAAA;ER6qCT;AQ5qCD;;EAAU,iBAAA;ERgrCT;AQ/qCD;;EAAU,iBAAA;ERmrCT;AQlrCD;;EAAU,iBAAA;ERsrCT;AQrrCD;;EAAU,iBAAA;ERyrCT;AQxrCD;;EAAU,iBAAA;ER4rCT;AQtrCD;EACE,kBAAA;ERwrCD;AQrrCD;EACE,qBAAA;EACA,iBAAA;EACA,kBAAA;EACA,kBAAA;ERurCD;AQlrCD;EAAA;IAFI,iBAAA;IRwrCD;EACF;AQhrCD;;EAEE,gBAAA;ERkrCD;AQ/qCD;;EAEE,2BAAA;EACA,eAAA;ERirCD;AQ7qCD;EAAuB,kBAAA;ERgrCtB;AQ/qCD;EAAuB,mBAAA;ERkrCtB;AQjrCD;EAAuB,oBAAA;ERorCtB;AQnrCD;EAAuB,qBAAA;ERsrCtB;AQrrCD;EAAuB,qBAAA;ERwrCtB;AQrrCD;EAAuB,2BAAA;ERwrCtB;AQvrCD;EAAuB,2BAAA;ER0rCtB;AQzrCD;EAAuB,4BAAA;ER4rCtB;AQzrCD;EACE,gBAAA;ER2rCD;AQzrCD;ECrGE,gBAAA;ETiyCD;AShyCC;EACE,gBAAA;ETkyCH;AQ5rCD;ECxGE,gBAAA;ETuyCD;AStyCC;EACE,gBAAA;ETwyCH;AQ/rCD;EC3GE,gBAAA;ET6yCD;AS5yCC;EACE,gBAAA;ET8yCH;AQlsCD;EC9GE,gBAAA;ETmzCD;ASlzCC;EACE,gBAAA;ETozCH;AQrsCD;ECjHE,gBAAA;ETyzCD;ASxzCC;EACE,gBAAA;ET0zCH;AQpsCD;EAGE,aAAA;EE3HA,2BAAA;EVg0CD;AU/zCC;EACE,2BAAA;EVi0CH;AQrsCD;EE9HE,2BAAA;EVs0CD;AUr0CC;EACE,2BAAA;EVu0CH;AQxsCD;EEjIE,2BAAA;EV40CD;AU30CC;EACE,2BAAA;EV60CH;AQ3sCD;EEpIE,2BAAA;EVk1CD;AUj1CC;EACE,2BAAA;EVm1CH;AQ9sCD;EEvIE,2BAAA;EVw1CD;AUv1CC;EACE,2BAAA;EVy1CH;AQ5sCD;EACE,qBAAA;EACA,qBAAA;EACA,kCAAA;ER8sCD;AQtsCD;;EAEE,eAAA;EACA,qBAAA;ERwsCD;AQ3sCD;;;;EAMI,kBAAA;ER2sCH;AQpsCD;EACE,iBAAA;EACA,kBAAA;ERssCD;AQlsCD;EALE,iBAAA;EACA,kBAAA;EAMA,mBAAA;ERqsCD;AQvsCD;EAKI,uBAAA;EACA,mBAAA;EACA,oBAAA;ERqsCH;AQhsCD;EACE,eAAA;EACA,qBAAA;ERksCD;AQhsCD;;EAEE,yBAAA;ERksCD;AQhsCD;EACE,mBAAA;ERksCD;AQhsCD;EACE,gBAAA;ERksCD;AQzqCD;EAAA;IAVM,aAAA;IACA,cAAA;IACA,aAAA;IACA,mBAAA;IGtNJ,kBAAA;IACA,yBAAA;IACA,qBAAA;IX84CC;EQnrCH;IAHM,oBAAA;IRyrCH;EACF;AQhrCD;;EAGE,cAAA;EACA,mCAAA;ERirCD;AQ/qCD;EACE,gBAAA;EACA,2BAAA;ERirCD;AQ7qCD;EACE,oBAAA;EACA,kBAAA;EACA,mBAAA;EACA,gCAAA;ER+qCD;AQ1qCG;;;EACE,kBAAA;ER8qCL;AQxrCD;;;EAmBI,gBAAA;EACA,gBAAA;EACA,yBAAA;EACA,gBAAA;ER0qCH;AQxqCG;;;EACE,wBAAA;ER4qCL;AQpqCD;;EAEE,qBAAA;EACA,iBAAA;EACA,iCAAA;EACA,gBAAA;EACA,mBAAA;ERsqCD;AQhqCG;;;;;;EAAW,aAAA;ERwqCd;AQvqCG;;;;;;EACE,wBAAA;ER8qCL;AQxqCD;EACE,qBAAA;EACA,oBAAA;EACA,yBAAA;ER0qCD;AYh9CD;;;;EAIE,gEAAA;EZk9CD;AY98CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EZg9CD;AY58CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EACA,wDAAA;UAAA,gDAAA;EZ88CD;AYp9CD;EASI,YAAA;EACA,iBAAA;EACA,mBAAA;EACA,0BAAA;UAAA,kBAAA;EZ88CH;AYz8CD;EACE,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,uBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EZ28CD;AYt9CD;EAeI,YAAA;EACA,oBAAA;EACA,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,kBAAA;EZ08CH;AYr8CD;EACE,mBAAA;EACA,oBAAA;EZu8CD;AajgDD;ECHE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;EdugDD;AajgDC;EAAA;IAFE,cAAA;IbugDD;EACF;AangDC;EAAA;IAFE,cAAA;IbygDD;EACF;AargDD;EAAA;IAFI,eAAA;Ib2gDD;EACF;AalgDD;ECvBE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;Ed4hDD;Aa//CD;ECvBE,oBAAA;EACA,qBAAA;EdyhDD;AezhDG;EACE,oBAAA;EAEA,iBAAA;EAEA,oBAAA;EACA,qBAAA;EfyhDL;AezgDG;EACE,aAAA;Ef2gDL;AepgDC;EACE,aAAA;EfsgDH;AevgDC;EACE,qBAAA;EfygDH;Ae1gDC;EACE,qBAAA;Ef4gDH;Ae7gDC;EACE,YAAA;Ef+gDH;AehhDC;EACE,qBAAA;EfkhDH;AenhDC;EACE,qBAAA;EfqhDH;AethDC;EACE,YAAA;EfwhDH;AezhDC;EACE,qBAAA;Ef2hDH;Ae5hDC;EACE,qBAAA;Ef8hDH;Ae/hDC;EACE,YAAA;EfiiDH;AeliDC;EACE,qBAAA;EfoiDH;AeriDC;EACE,oBAAA;EfuiDH;AezhDC;EACE,aAAA;Ef2hDH;Ae5hDC;EACE,qBAAA;Ef8hDH;Ae/hDC;EACE,qBAAA;EfiiDH;AeliDC;EACE,YAAA;EfoiDH;AeriDC;EACE,qBAAA;EfuiDH;AexiDC;EACE,qBAAA;Ef0iDH;Ae3iDC;EACE,YAAA;Ef6iDH;Ae9iDC;EACE,qBAAA;EfgjDH;AejjDC;EACE,qBAAA;EfmjDH;AepjDC;EACE,YAAA;EfsjDH;AevjDC;EACE,qBAAA;EfyjDH;Ae1jDC;EACE,oBAAA;Ef4jDH;AexjDC;EACE,aAAA;Ef0jDH;Ae1kDC;EACE,YAAA;Ef4kDH;Ae7kDC;EACE,oBAAA;Ef+kDH;AehlDC;EACE,oBAAA;EfklDH;AenlDC;EACE,WAAA;EfqlDH;AetlDC;EACE,oBAAA;EfwlDH;AezlDC;EACE,oBAAA;Ef2lDH;Ae5lDC;EACE,WAAA;Ef8lDH;Ae/lDC;EACE,oBAAA;EfimDH;AelmDC;EACE,oBAAA;EfomDH;AermDC;EACE,WAAA;EfumDH;AexmDC;EACE,oBAAA;Ef0mDH;Ae3mDC;EACE,mBAAA;Ef6mDH;AezmDC;EACE,YAAA;Ef2mDH;Ae7lDC;EACE,mBAAA;Ef+lDH;AehmDC;EACE,2BAAA;EfkmDH;AenmDC;EACE,2BAAA;EfqmDH;AetmDC;EACE,kBAAA;EfwmDH;AezmDC;EACE,2BAAA;Ef2mDH;Ae5mDC;EACE,2BAAA;Ef8mDH;Ae/mDC;EACE,kBAAA;EfinDH;AelnDC;EACE,2BAAA;EfonDH;AernDC;EACE,2BAAA;EfunDH;AexnDC;EACE,kBAAA;Ef0nDH;Ae3nDC;EACE,2BAAA;Ef6nDH;Ae9nDC;EACE,0BAAA;EfgoDH;AejoDC;EACE,iBAAA;EfmoDH;AanoDD;EElCI;IACE,aAAA;IfwqDH;EejqDD;IACE,aAAA;IfmqDD;EepqDD;IACE,qBAAA;IfsqDD;EevqDD;IACE,qBAAA;IfyqDD;Ee1qDD;IACE,YAAA;If4qDD;Ee7qDD;IACE,qBAAA;If+qDD;EehrDD;IACE,qBAAA;IfkrDD;EenrDD;IACE,YAAA;IfqrDD;EetrDD;IACE,qBAAA;IfwrDD;EezrDD;IACE,qBAAA;If2rDD;Ee5rDD;IACE,YAAA;If8rDD;Ee/rDD;IACE,qBAAA;IfisDD;EelsDD;IACE,oBAAA;IfosDD;EetrDD;IACE,aAAA;IfwrDD;EezrDD;IACE,qBAAA;If2rDD;Ee5rDD;IACE,qBAAA;If8rDD;Ee/rDD;IACE,YAAA;IfisDD;EelsDD;IACE,qBAAA;IfosDD;EersDD;IACE,qBAAA;IfusDD;EexsDD;IACE,YAAA;If0sDD;Ee3sDD;IACE,qBAAA;If6sDD;Ee9sDD;IACE,qBAAA;IfgtDD;EejtDD;IACE,YAAA;IfmtDD;EeptDD;IACE,qBAAA;IfstDD;EevtDD;IACE,oBAAA;IfytDD;EertDD;IACE,aAAA;IfutDD;EevuDD;IACE,YAAA;IfyuDD;Ee1uDD;IACE,oBAAA;If4uDD;Ee7uDD;IACE,oBAAA;If+uDD;EehvDD;IACE,WAAA;IfkvDD;EenvDD;IACE,oBAAA;IfqvDD;EetvDD;IACE,oBAAA;IfwvDD;EezvDD;IACE,WAAA;If2vDD;Ee5vDD;IACE,oBAAA;If8vDD;Ee/vDD;IACE,oBAAA;IfiwDD;EelwDD;IACE,WAAA;IfowDD;EerwDD;IACE,oBAAA;IfuwDD;EexwDD;IACE,mBAAA;If0wDD;EetwDD;IACE,YAAA;IfwwDD;Ee1vDD;IACE,mBAAA;If4vDD;Ee7vDD;IACE,2BAAA;If+vDD;EehwDD;IACE,2BAAA;IfkwDD;EenwDD;IACE,kBAAA;IfqwDD;EetwDD;IACE,2BAAA;IfwwDD;EezwDD;IACE,2BAAA;If2wDD;Ee5wDD;IACE,kBAAA;If8wDD;Ee/wDD;IACE,2BAAA;IfixDD;EelxDD;IACE,2BAAA;IfoxDD;EerxDD;IACE,kBAAA;IfuxDD;EexxDD;IACE,2BAAA;If0xDD;Ee3xDD;IACE,0BAAA;If6xDD;Ee9xDD;IACE,iBAAA;IfgyDD;EACF;AaxxDD;EE3CI;IACE,aAAA;Ifs0DH;Ee/zDD;IACE,aAAA;Ifi0DD;Eel0DD;IACE,qBAAA;Ifo0DD;Eer0DD;IACE,qBAAA;Ifu0DD;Eex0DD;IACE,YAAA;If00DD;Ee30DD;IACE,qBAAA;If60DD;Ee90DD;IACE,qBAAA;Ifg1DD;Eej1DD;IACE,YAAA;Ifm1DD;Eep1DD;IACE,qBAAA;Ifs1DD;Eev1DD;IACE,qBAAA;Ify1DD;Ee11DD;IACE,YAAA;If41DD;Ee71DD;IACE,qBAAA;If+1DD;Eeh2DD;IACE,oBAAA;Ifk2DD;Eep1DD;IACE,aAAA;Ifs1DD;Eev1DD;IACE,qBAAA;Ify1DD;Ee11DD;IACE,qBAAA;If41DD;Ee71DD;IACE,YAAA;If+1DD;Eeh2DD;IACE,qBAAA;Ifk2DD;Een2DD;IACE,qBAAA;Ifq2DD;Eet2DD;IACE,YAAA;Ifw2DD;Eez2DD;IACE,qBAAA;If22DD;Ee52DD;IACE,qBAAA;If82DD;Ee/2DD;IACE,YAAA;Ifi3DD;Eel3DD;IACE,qBAAA;Ifo3DD;Eer3DD;IACE,oBAAA;Ifu3DD;Een3DD;IACE,aAAA;Ifq3DD;Eer4DD;IACE,YAAA;Ifu4DD;Eex4DD;IACE,oBAAA;If04DD;Ee34DD;IACE,oBAAA;If64DD;Ee94DD;IACE,WAAA;Ifg5DD;Eej5DD;IACE,oBAAA;Ifm5DD;Eep5DD;IACE,oBAAA;Ifs5DD;Eev5DD;IACE,WAAA;Ify5DD;Ee15DD;IACE,oBAAA;If45DD;Ee75DD;IACE,oBAAA;If+5DD;Eeh6DD;IACE,WAAA;Ifk6DD;Een6DD;IACE,oBAAA;Ifq6DD;Eet6DD;IACE,mBAAA;Ifw6DD;Eep6DD;IACE,YAAA;Ifs6DD;Eex5DD;IACE,mBAAA;If05DD;Ee35DD;IACE,2BAAA;If65DD;Ee95DD;IACE,2BAAA;Ifg6DD;Eej6DD;IACE,kBAAA;Ifm6DD;Eep6DD;IACE,2BAAA;Ifs6DD;Eev6DD;IACE,2BAAA;Ify6DD;Ee16DD;IACE,kBAAA;If46DD;Ee76DD;IACE,2BAAA;If+6DD;Eeh7DD;IACE,2BAAA;Ifk7DD;Een7DD;IACE,kBAAA;Ifq7DD;Eet7DD;IACE,2BAAA;Ifw7DD;Eez7DD;IACE,0BAAA;If27DD;Ee57DD;IACE,iBAAA;If87DD;EACF;Aan7DD;EE9CI;IACE,aAAA;Ifo+DH;Ee79DD;IACE,aAAA;If+9DD;Eeh+DD;IACE,qBAAA;Ifk+DD;Een+DD;IACE,qBAAA;Ifq+DD;Eet+DD;IACE,YAAA;Ifw+DD;Eez+DD;IACE,qBAAA;If2+DD;Ee5+DD;IACE,qBAAA;If8+DD;Ee/+DD;IACE,YAAA;Ifi/DD;Eel/DD;IACE,qBAAA;Ifo/DD;Eer/DD;IACE,qBAAA;Ifu/DD;Eex/DD;IACE,YAAA;If0/DD;Ee3/DD;IACE,qBAAA;If6/DD;Ee9/DD;IACE,oBAAA;IfggED;Eel/DD;IACE,aAAA;Ifo/DD;Eer/DD;IACE,qBAAA;Ifu/DD;Eex/DD;IACE,qBAAA;If0/DD;Ee3/DD;IACE,YAAA;If6/DD;Ee9/DD;IACE,qBAAA;IfggED;EejgED;IACE,qBAAA;IfmgED;EepgED;IACE,YAAA;IfsgED;EevgED;IACE,qBAAA;IfygED;Ee1gED;IACE,qBAAA;If4gED;Ee7gED;IACE,YAAA;If+gED;EehhED;IACE,qBAAA;IfkhED;EenhED;IACE,oBAAA;IfqhED;EejhED;IACE,aAAA;IfmhED;EeniED;IACE,YAAA;IfqiED;EetiED;IACE,oBAAA;IfwiED;EeziED;IACE,oBAAA;If2iED;Ee5iED;IACE,WAAA;If8iED;Ee/iED;IACE,oBAAA;IfijED;EeljED;IACE,oBAAA;IfojED;EerjED;IACE,WAAA;IfujED;EexjED;IACE,oBAAA;If0jED;Ee3jED;IACE,oBAAA;If6jED;Ee9jED;IACE,WAAA;IfgkED;EejkED;IACE,oBAAA;IfmkED;EepkED;IACE,mBAAA;IfskED;EelkED;IACE,YAAA;IfokED;EetjED;IACE,mBAAA;IfwjED;EezjED;IACE,2BAAA;If2jED;Ee5jED;IACE,2BAAA;If8jED;Ee/jED;IACE,kBAAA;IfikED;EelkED;IACE,2BAAA;IfokED;EerkED;IACE,2BAAA;IfukED;EexkED;IACE,kBAAA;If0kED;Ee3kED;IACE,2BAAA;If6kED;Ee9kED;IACE,2BAAA;IfglED;EejlED;IACE,kBAAA;IfmlED;EeplED;IACE,2BAAA;IfslED;EevlED;IACE,0BAAA;IfylED;Ee1lED;IACE,iBAAA;If4lED;EACF;AgBhqED;EACE,+BAAA;EhBkqED;AgBhqED;EACE,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,kBAAA;EhBkqED;AgBhqED;EACE,kBAAA;EhBkqED;AgB5pED;EACE,aAAA;EACA,iBAAA;EACA,qBAAA;EhB8pED;AgBjqED;;;;;;EAWQ,cAAA;EACA,yBAAA;EACA,qBAAA;EACA,+BAAA;EhB8pEP;AgB5qED;EAoBI,wBAAA;EACA,kCAAA;EhB2pEH;AgBhrED;;;;;;EA8BQ,eAAA;EhB0pEP;AgBxrED;EAoCI,+BAAA;EhBupEH;AgB3rED;EAyCI,2BAAA;EhBqpEH;AgB9oED;;;;;;EAOQ,cAAA;EhB+oEP;AgBpoED;EACE,2BAAA;EhBsoED;AgBvoED;;;;;;EAQQ,2BAAA;EhBuoEP;AgB/oED;;EAeM,0BAAA;EhBooEL;AgB1nED;EAEI,2BAAA;EhB2nEH;AgBlnED;EAEI,2BAAA;EhBmnEH;AgB1mED;EACE,kBAAA;EACA,aAAA;EACA,uBAAA;EhB4mED;AgBvmEG;;EACE,kBAAA;EACA,aAAA;EACA,qBAAA;EhB0mEL;AiBtvEC;;;;;;;;;;;;EAOI,2BAAA;EjB6vEL;AiBvvEC;;;;;EAMI,2BAAA;EjBwvEL;AiB3wEC;;;;;;;;;;;;EAOI,2BAAA;EjBkxEL;AiB5wEC;;;;;EAMI,2BAAA;EjB6wEL;AiBhyEC;;;;;;;;;;;;EAOI,2BAAA;EjBuyEL;AiBjyEC;;;;;EAMI,2BAAA;EjBkyEL;AiBrzEC;;;;;;;;;;;;EAOI,2BAAA;EjB4zEL;AiBtzEC;;;;;EAMI,2BAAA;EjBuzEL;AiB10EC;;;;;;;;;;;;EAOI,2BAAA;EjBi1EL;AiB30EC;;;;;EAMI,2BAAA;EjB40EL;AgB1rED;EACE,kBAAA;EACA,mBAAA;EhB4rED;AgB/nED;EAAA;IA1DI,aAAA;IACA,qBAAA;IACA,oBAAA;IACA,8CAAA;IACA,2BAAA;IhB6rED;EgBvoEH;IAlDM,kBAAA;IhB4rEH;EgB1oEH;;;;;;IAzCY,qBAAA;IhB2rET;EgBlpEH;IAjCM,WAAA;IhBsrEH;EgBrpEH;;;;;;IAxBY,gBAAA;IhBqrET;EgB7pEH;;;;;;IApBY,iBAAA;IhByrET;EgBrqEH;;;;IAPY,kBAAA;IhBkrET;EACF;AkB54ED;EACE,YAAA;EACA,WAAA;EACA,WAAA;EAIA,cAAA;ElB24ED;AkBx4ED;EACE,gBAAA;EACA,aAAA;EACA,YAAA;EACA,qBAAA;EACA,iBAAA;EACA,sBAAA;EACA,gBAAA;EACA,WAAA;EACA,kCAAA;ElB04ED;AkBv4ED;EACE,uBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;ElBy4ED;AkB93ED;Eb4BE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELq2ET;AkB93ED;;EAEE,iBAAA;EACA,oBAAA;EACA,qBAAA;ElBg4ED;AkB53ED;EACE,gBAAA;ElB83ED;AkB13ED;EACE,gBAAA;EACA,aAAA;ElB43ED;AkBx3ED;;EAEE,cAAA;ElB03ED;AkBt3ED;;;EZxEE,sBAAA;EAEA,4CAAA;EACA,sBAAA;ENk8ED;AkBt3ED;EACE,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;ElBw3ED;AkB91ED;EACE,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EACA,wBAAA;EACA,2BAAA;EACA,oBAAA;EbzDA,0DAAA;EACQ,kDAAA;EAyHR,wFAAA;EACK,2EAAA;EACG,wEAAA;ELkyET;AmB16EC;EACE,uBAAA;EACA,YAAA;EdUF,wFAAA;EACQ,gFAAA;ELm6ET;AKl4EC;EACE,gBAAA;EACA,YAAA;ELo4EH;AKl4EC;EAA0B,gBAAA;ELq4E3B;AKp4EC;EAAgC,gBAAA;ELu4EjC;AkBt2EC;;;EAGE,qBAAA;EACA,2BAAA;EACA,YAAA;ElBw2EH;AkBp2EC;EACE,cAAA;ElBs2EH;AkB11ED;EACE,0BAAA;ElB41ED;AkBxzED;EAxBE;;;;IAIE,mBAAA;IlBm1ED;EkBj1EC;;;;;;;;IAEE,mBAAA;IlBy1EH;EkBt1EC;;;;;;;;IAEE,mBAAA;IlB81EH;EACF;AkBp1ED;EACE,qBAAA;ElBs1ED;AkB90ED;;EAEE,oBAAA;EACA,gBAAA;EACA,kBAAA;EACA,qBAAA;ElBg1ED;AkBr1ED;;EAQI,kBAAA;EACA,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,iBAAA;ElBi1EH;AkB90ED;;;;EAIE,oBAAA;EACA,oBAAA;EACA,oBAAA;ElBg1ED;AkB70ED;;EAEE,kBAAA;ElB+0ED;AkB30ED;;EAEE,uBAAA;EACA,oBAAA;EACA,kBAAA;EACA,wBAAA;EACA,qBAAA;EACA,iBAAA;ElB60ED;AkB30ED;;EAEE,eAAA;EACA,mBAAA;ElB60ED;AkBp0EC;;;;;;EAGE,qBAAA;ElBy0EH;AkBn0EC;;;;EAEE,qBAAA;ElBu0EH;AkBj0EC;;;;EAGI,qBAAA;ElBo0EL;AkBzzED;EAEE,kBAAA;EACA,qBAAA;EAEA,kBAAA;ElByzED;AkBvzEC;;EAEE,iBAAA;EACA,kBAAA;ElByzEH;AkB5yED;ECpPE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBmiFD;AmBjiFC;EACE,cAAA;EACA,mBAAA;EnBmiFH;AmBhiFC;;EAEE,cAAA;EnBkiFH;AkBxzED;ECvPE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBkjFD;AmBhjFC;EACE,cAAA;EACA,mBAAA;EnBkjFH;AmB/iFC;;EAEE,cAAA;EnBijFH;AkBv0ED;EAKI,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;ElBq0EH;AkBj0ED;ECnQE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBukFD;AmBrkFC;EACE,cAAA;EACA,mBAAA;EnBukFH;AmBpkFC;;EAEE,cAAA;EnBskFH;AkB70ED;ECtQE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBslFD;AmBplFC;EACE,cAAA;EACA,mBAAA;EnBslFH;AmBnlFC;;EAEE,cAAA;EnBqlFH;AkB51ED;EAKI,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;ElB01EH;AkBj1ED;EAEE,oBAAA;ElBk1ED;AkBp1ED;EAMI,uBAAA;ElBi1EH;AkB70ED;EACE,oBAAA;EACA,QAAA;EACA,UAAA;EACA,YAAA;EACA,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,oBAAA;EACA,sBAAA;ElB+0ED;AkB70ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB+0ED;AkB70ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB+0ED;AkB30ED;;;;;;;;;;EC7WI,gBAAA;EnBosFH;AkBv1ED;ECzWI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELqpFT;AmBnsFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;EL0pFT;AkBj2ED;EC/VI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBmsFH;AkBt2ED;ECzVI,gBAAA;EnBksFH;AkBt2ED;;;;;;;;;;EChXI,gBAAA;EnBkuFH;AkBl3ED;EC5WI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELmrFT;AmBjuFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;ELwrFT;AkB53ED;EClWI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBiuFH;AkBj4ED;EC5VI,gBAAA;EnBguFH;AkBj4ED;;;;;;;;;;ECnXI,gBAAA;EnBgwFH;AkB74ED;EC/WI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELitFT;AmB/vFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;ELstFT;AkBv5ED;ECrWI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnB+vFH;AkB55ED;EC/VI,gBAAA;EnB8vFH;AkBx5EC;EACG,WAAA;ElB05EJ;AkBx5EC;EACG,QAAA;ElB05EJ;AkBh5ED;EACE,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;ElBk5ED;AkB/zED;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlBi4EH;EkBr0EH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlB+3EH;EkB10EH;IAhDM,uBAAA;IlB63EH;EkB70EH;IA5CM,uBAAA;IACA,wBAAA;IlB43EH;EkBj1EH;;;IAtCQ,aAAA;IlB43EL;EkBt1EH;IAhCM,aAAA;IlBy3EH;EkBz1EH;IA5BM,kBAAA;IACA,wBAAA;IlBw3EH;EkB71EH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlBq3EH;EkBp2EH;;IAdQ,iBAAA;IlBs3EL;EkBx2EH;;IATM,oBAAA;IACA,gBAAA;IlBq3EH;EkB72EH;IAHM,QAAA;IlBm3EH;EACF;AkBz2ED;;;;EASI,eAAA;EACA,kBAAA;EACA,kBAAA;ElBs2EH;AkBj3ED;;EAiBI,kBAAA;ElBo2EH;AkBr3ED;EJzeE,oBAAA;EACA,qBAAA;Edi2FD;AkBl1EC;EAAA;IAVI,mBAAA;IACA,kBAAA;IACA,kBAAA;IlBg2EH;EACF;AkBh4ED;EAwCI,aAAA;ElB21EH;AkB90EC;EAAA;IAHM,0BAAA;IlBq1EL;EACF;AkB50EC;EAAA;IAHM,kBAAA;IlBm1EL;EACF;AoB73FD;EACE,uBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,wBAAA;EACA,gCAAA;MAAA,4BAAA;EACA,iBAAA;EACA,wBAAA;EACA,+BAAA;EACA,qBAAA;EC6BA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,oBAAA;EhB4KA,2BAAA;EACG,wBAAA;EACC,uBAAA;EACI,mBAAA;ELwrFT;AoBh4FG;;;;;;EdrBF,sBAAA;EAEA,4CAAA;EACA,sBAAA;EN45FD;AoBp4FC;;;EAGE,gBAAA;EACA,uBAAA;EpBs4FH;AoBn4FC;;EAEE,YAAA;EACA,wBAAA;Ef2BF,0DAAA;EACQ,kDAAA;EL22FT;AoBn4FC;;;EAGE,qBAAA;EACA,sBAAA;EE9CF,eAAA;EAGA,2BAAA;EjB8DA,0BAAA;EACQ,kBAAA;ELq3FT;AoB/3FD;ECrDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBu7FD;AqBr7FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBu7FP;AqBr7FC;;;EAGE,wBAAA;ErBu7FH;AqBl7FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBg8FT;AoBx6FD;ECnBI,gBAAA;EACA,2BAAA;ErB87FH;AoBz6FD;ECxDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBo+FD;AqBl+FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBo+FP;AqBl+FC;;;EAGE,wBAAA;ErBo+FH;AqB/9FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB6+FT;AoBl9FD;ECtBI,gBAAA;EACA,2BAAA;ErB2+FH;AoBl9FD;EC5DE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBihGD;AqB/gGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBihGP;AqB/gGC;;;EAGE,wBAAA;ErBihGH;AqB5gGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB0hGT;AoB3/FD;EC1BI,gBAAA;EACA,2BAAA;ErBwhGH;AoB3/FD;EChEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB8jGD;AqB5jGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB8jGP;AqB5jGC;;;EAGE,wBAAA;ErB8jGH;AqBzjGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBukGT;AoBpiGD;EC9BI,gBAAA;EACA,2BAAA;ErBqkGH;AoBpiGD;ECpEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB2mGD;AqBzmGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB2mGP;AqBzmGC;;;EAGE,wBAAA;ErB2mGH;AqBtmGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBonGT;AoB7kGD;EClCI,gBAAA;EACA,2BAAA;ErBknGH;AoB7kGD;ECxEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBwpGD;AqBtpGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBwpGP;AqBtpGC;;;EAGE,wBAAA;ErBwpGH;AqBnpGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBiqGT;AoBtnGD;ECtCI,gBAAA;EACA,2BAAA;ErB+pGH;AoBjnGD;EACE,gBAAA;EACA,qBAAA;EACA,kBAAA;EpBmnGD;AoBjnGC;;;;;EAKE,+BAAA;Ef7BF,0BAAA;EACQ,kBAAA;ELipGT;AoBlnGC;;;;EAIE,2BAAA;EpBonGH;AoBlnGC;;EAEE,gBAAA;EACA,4BAAA;EACA,+BAAA;EpBonGH;AoBhnGG;;;;EAEE,gBAAA;EACA,uBAAA;EpBonGL;AoB3mGD;;EC/EE,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;ErB8rGD;AoB9mGD;;ECnFE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErBqsGD;AoBjnGD;;ECvFE,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErB4sGD;AoBhnGD;EACE,gBAAA;EACA,aAAA;EpBknGD;AoB9mGD;EACE,iBAAA;EpBgnGD;AoBzmGC;;;EACE,aAAA;EpB6mGH;AuBjwGD;EACE,YAAA;ElBoLA,0CAAA;EACK,qCAAA;EACG,kCAAA;ELglGT;AuBpwGC;EACE,YAAA;EvBswGH;AuBlwGD;EACE,eAAA;EACA,oBAAA;EvBowGD;AuBlwGC;EAAY,gBAAA;EAAgB,qBAAA;EvBswG7B;AuBrwGC;EAAY,oBAAA;EvBwwGb;AuBvwGC;EAAY,0BAAA;EvB0wGb;AuBvwGD;EACE,oBAAA;EACA,WAAA;EACA,kBAAA;ElBsKA,iDAAA;EACQ,4CAAA;KAAA,yCAAA;EAOR,oCAAA;EACQ,+BAAA;KAAA,4BAAA;EAGR,0CAAA;EACQ,qCAAA;KAAA,kCAAA;EL4lGT;AwBtyGD;EACE,uBAAA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;EACA,qCAAA;EACA,oCAAA;ExBwyGD;AwBpyGD;;EAEE,oBAAA;ExBsyGD;AwBlyGD;EACE,YAAA;ExBoyGD;AwBhyGD;EACE,oBAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,2BAAA;EACA,2BAAA;EACA,uCAAA;EACA,oBAAA;EnBuBA,qDAAA;EACQ,6CAAA;EmBtBR,sCAAA;UAAA,8BAAA;ExBmyGD;AwB9xGC;EACE,UAAA;EACA,YAAA;ExBgyGH;AwBzzGD;ECxBE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzBo1GD;AwB/zGD;EAmCI,gBAAA;EACA,mBAAA;EACA,aAAA;EACA,qBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExB+xGH;AwBzxGC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;ExB2xGH;AwBrxGC;;;EAGE,gBAAA;EACA,uBAAA;EACA,YAAA;EACA,2BAAA;ExBuxGH;AwB9wGC;;;EAGE,gBAAA;ExBgxGH;AwB5wGC;;EAEE,uBAAA;EACA,+BAAA;EACA,wBAAA;EE1GF,qEAAA;EF4GE,qBAAA;ExB8wGH;AwBzwGD;EAGI,gBAAA;ExBywGH;AwB5wGD;EAQI,YAAA;ExBuwGH;AwB/vGD;EACE,YAAA;EACA,UAAA;ExBiwGD;AwBzvGD;EACE,SAAA;EACA,aAAA;ExB2vGD;AwBvvGD;EACE,gBAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExByvGD;AwBrvGD;EACE,iBAAA;EACA,SAAA;EACA,UAAA;EACA,WAAA;EACA,QAAA;EACA,cAAA;ExBuvGD;AwBnvGD;EACE,UAAA;EACA,YAAA;ExBqvGD;AwB7uGD;;EAII,eAAA;EACA,0BAAA;EACA,aAAA;ExB6uGH;AwBnvGD;;EAUI,WAAA;EACA,cAAA;EACA,oBAAA;ExB6uGH;AwBxtGD;EAXE;IAnEA,YAAA;IACA,UAAA;IxB0yGC;EwBxuGD;IAzDA,SAAA;IACA,aAAA;IxBoyGC;EACF;A2Bn7GD;;EAEE,oBAAA;EACA,uBAAA;EACA,wBAAA;E3Bq7GD;A2Bz7GD;;EAMI,oBAAA;EACA,aAAA;E3Bu7GH;A2Br7GG;;;;;;;;EAIE,YAAA;E3B27GL;A2Br7GD;;;;EAKI,mBAAA;E3Bs7GH;A2Bj7GD;EACE,mBAAA;E3Bm7GD;A2Bp7GD;;EAMI,aAAA;E3Bk7GH;A2Bx7GD;;;EAWI,kBAAA;E3Bk7GH;A2B96GD;EACE,kBAAA;E3Bg7GD;A2B56GD;EACE,gBAAA;E3B86GD;A2B76GC;ECjDA,+BAAA;EACG,4BAAA;E5Bi+GJ;A2B56GD;;EC9CE,8BAAA;EACG,2BAAA;E5B89GJ;A2B36GD;EACE,aAAA;E3B66GD;A2B36GD;EACE,kBAAA;E3B66GD;A2B36GD;;EClEE,+BAAA;EACG,4BAAA;E5Bi/GJ;A2B16GD;EChEE,8BAAA;EACG,2BAAA;E5B6+GJ;A2Bz6GD;;EAEE,YAAA;E3B26GD;A2B15GD;EACE,mBAAA;EACA,oBAAA;E3B45GD;A2B15GD;EACE,oBAAA;EACA,qBAAA;E3B45GD;A2Bv5GD;EtB9CE,0DAAA;EACQ,kDAAA;ELw8GT;A2Bv5GC;EtBlDA,0BAAA;EACQ,kBAAA;EL48GT;A2Bp5GD;EACE,gBAAA;E3Bs5GD;A2Bn5GD;EACE,yBAAA;EACA,wBAAA;E3Bq5GD;A2Bl5GD;EACE,yBAAA;E3Bo5GD;A2B74GD;;;EAII,gBAAA;EACA,aAAA;EACA,aAAA;EACA,iBAAA;E3B84GH;A2Br5GD;EAcM,aAAA;E3B04GL;A2Bx5GD;;;;EAsBI,kBAAA;EACA,gBAAA;E3Bw4GH;A2Bn4GC;EACE,kBAAA;E3Bq4GH;A2Bn4GC;EACE,8BAAA;ECnKF,+BAAA;EACC,8BAAA;E5ByiHF;A2Bp4GC;EACE,gCAAA;EC/KF,4BAAA;EACC,2BAAA;E5BsjHF;A2Bp4GD;EACE,kBAAA;E3Bs4GD;A2Bp4GD;;EC9KE,+BAAA;EACC,8BAAA;E5BsjHF;A2Bn4GD;EC5LE,4BAAA;EACC,2BAAA;E5BkkHF;A2B/3GD;EACE,gBAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;E3Bi4GD;A2Br4GD;;EAOI,aAAA;EACA,qBAAA;EACA,WAAA;E3Bk4GH;A2B34GD;EAYI,aAAA;E3Bk4GH;A2B94GD;EAgBI,YAAA;E3Bi4GH;A2Bh3GD;;;;EAKM,oBAAA;EACA,wBAAA;EACA,sBAAA;E3Bi3GL;A6B1lHD;EACE,oBAAA;EACA,gBAAA;EACA,2BAAA;E7B4lHD;A6BzlHC;EACE,aAAA;EACA,iBAAA;EACA,kBAAA;E7B2lHH;A6BpmHD;EAeI,oBAAA;EACA,YAAA;EAKA,aAAA;EAEA,aAAA;EACA,kBAAA;E7BmlHH;A6B1kHD;;;EV8BE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBijHD;AmB/iHC;;;EACE,cAAA;EACA,mBAAA;EnBmjHH;AmBhjHC;;;;;;EAEE,cAAA;EnBsjHH;A6B5lHD;;;EVyBE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBwkHD;AmBtkHC;;;EACE,cAAA;EACA,mBAAA;EnB0kHH;AmBvkHC;;;;;;EAEE,cAAA;EnB6kHH;A6B1mHD;;;EAGE,qBAAA;E7B4mHD;A6B1mHC;;;EACE,kBAAA;E7B8mHH;A6B1mHD;;EAEE,WAAA;EACA,qBAAA;EACA,wBAAA;E7B4mHD;A6BvmHD;EACE,mBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;E7BymHD;A6BtmHC;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;E7BwmHH;A6BtmHC;EACE,oBAAA;EACA,iBAAA;EACA,oBAAA;E7BwmHH;A6B5nHD;;EA0BI,eAAA;E7BsmHH;A6BjmHD;;;;;;;EDhGE,+BAAA;EACG,4BAAA;E5B0sHJ;A6BlmHD;EACE,iBAAA;E7BomHD;A6BlmHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;E5B+sHJ;A6BnmHD;EACE,gBAAA;E7BqmHD;A6BhmHD;EACE,oBAAA;EAGA,cAAA;EACA,qBAAA;E7BgmHD;A6BrmHD;EAUI,oBAAA;E7B8lHH;A6BxmHD;EAYM,mBAAA;E7B+lHL;A6B5lHG;;;EAGE,YAAA;E7B8lHL;A6BzlHC;;EAGI,oBAAA;E7B0lHL;A6BvlHC;;EAGI,mBAAA;E7BwlHL;A8BlvHD;EACE,kBAAA;EACA,iBAAA;EACA,kBAAA;E9BovHD;A8BvvHD;EAOI,oBAAA;EACA,gBAAA;E9BmvHH;A8B3vHD;EAWM,oBAAA;EACA,gBAAA;EACA,oBAAA;E9BmvHL;A8BlvHK;;EAEE,uBAAA;EACA,2BAAA;E9BovHP;A8B/uHG;EACE,gBAAA;E9BivHL;A8B/uHK;;EAEE,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,qBAAA;E9BivHP;A8B1uHG;;;EAGE,2BAAA;EACA,uBAAA;E9B4uHL;A8BrxHD;ELHE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzB2xHD;A8B3xHD;EA0DI,iBAAA;E9BouHH;A8B3tHD;EACE,kCAAA;E9B6tHD;A8B9tHD;EAGI,aAAA;EAEA,qBAAA;E9B6tHH;A8BluHD;EASM,mBAAA;EACA,yBAAA;EACA,+BAAA;EACA,4BAAA;E9B4tHL;A8B3tHK;EACE,uCAAA;E9B6tHP;A8BvtHK;;;EAGE,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,kCAAA;EACA,iBAAA;E9BytHP;A8BptHC;EAqDA,aAAA;EA8BA,kBAAA;E9BqoHD;A8BxtHC;EAwDE,aAAA;E9BmqHH;A8B3tHC;EA0DI,oBAAA;EACA,oBAAA;E9BoqHL;A8B/tHC;EAgEE,WAAA;EACA,YAAA;E9BkqHH;A8BtpHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BiqHH;E8B3pHH;IAJQ,kBAAA;I9BkqHL;EACF;A8B5uHC;EAuFE,iBAAA;EACA,oBAAA;E9BwpHH;A8BhvHC;;;EA8FE,2BAAA;E9BupHH;A8BzoHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9BspHH;E8B9oHH;;;IAHM,8BAAA;I9BspHH;EACF;A8BvvHD;EAEI,aAAA;E9BwvHH;A8B1vHD;EAMM,oBAAA;E9BuvHL;A8B7vHD;EASM,kBAAA;E9BuvHL;A8BlvHK;;;EAGE,gBAAA;EACA,2BAAA;E9BovHP;A8B5uHD;EAEI,aAAA;E9B6uHH;A8B/uHD;EAIM,iBAAA;EACA,gBAAA;E9B8uHL;A8BluHD;EACE,aAAA;E9BouHD;A8BruHD;EAII,aAAA;E9BouHH;A8BxuHD;EAMM,oBAAA;EACA,oBAAA;E9BquHL;A8B5uHD;EAYI,WAAA;EACA,YAAA;E9BmuHH;A8BvtHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BkuHH;E8B5tHH;IAJQ,kBAAA;I9BmuHL;EACF;A8B3tHD;EACE,kBAAA;E9B6tHD;A8B9tHD;EAKI,iBAAA;EACA,oBAAA;E9B4tHH;A8BluHD;;;EAYI,2BAAA;E9B2tHH;A8B7sHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9B0tHH;E8BltHH;;;IAHM,8BAAA;I9B0tHH;EACF;A8BjtHD;EAEI,eAAA;EACA,oBAAA;E9BktHH;A8BrtHD;EAMI,gBAAA;EACA,qBAAA;E9BktHH;A8BzsHD;EAEE,kBAAA;EF7OA,4BAAA;EACC,2BAAA;E5Bw7HF;A+Bl7HD;EACE,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,+BAAA;E/Bo7HD;A+B56HD;EAAA;IAFI,oBAAA;I/Bk7HD;EACF;A+Bn6HD;EAAA;IAFI,aAAA;I/By6HD;EACF;A+B35HD;EACE,qBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,4DAAA;UAAA,oDAAA;EAEA,mCAAA;E/B45HD;A+B15HC;EACE,kBAAA;E/B45HH;A+B/3HD;EAAA;IAzBI,aAAA;IACA,eAAA;IACA,0BAAA;YAAA,kBAAA;I/B45HD;E+B15HC;IACE,2BAAA;IACA,gCAAA;IACA,yBAAA;IACA,mBAAA;IACA,8BAAA;I/B45HH;E+Bz5HC;IACE,qBAAA;I/B25HH;E+Bt5HC;;;IAGE,iBAAA;IACA,kBAAA;I/Bw5HH;EACF;A+Bp5HD;;EAGI,mBAAA;E/Bq5HH;A+Bh5HC;EAAA;;IAFI,mBAAA;I/Bu5HH;EACF;A+B94HD;;;;EAII,qBAAA;EACA,oBAAA;E/Bg5HH;A+B14HC;EAAA;;;;IAHI,iBAAA;IACA,gBAAA;I/Bo5HH;EACF;A+Bx4HD;EACE,eAAA;EACA,uBAAA;E/B04HD;A+Br4HD;EAAA;IAFI,kBAAA;I/B24HD;EACF;A+Bv4HD;;EAEE,iBAAA;EACA,UAAA;EACA,SAAA;EACA,eAAA;E/By4HD;A+Bn4HD;EAAA;;IAFI,kBAAA;I/B04HD;EACF;A+Bx4HD;EACE,QAAA;EACA,uBAAA;E/B04HD;A+Bx4HD;EACE,WAAA;EACA,kBAAA;EACA,uBAAA;E/B04HD;A+Bp4HD;EACE,aAAA;EACA,oBAAA;EACA,iBAAA;EACA,mBAAA;EACA,cAAA;E/Bs4HD;A+Bp4HC;;EAEE,uBAAA;E/Bs4HH;A+B/4HD;EAaI,gBAAA;E/Bq4HH;A+B53HD;EALI;;IAEE,oBAAA;I/Bo4HH;EACF;A+B13HD;EACE,oBAAA;EACA,cAAA;EACA,oBAAA;EACA,mBAAA;EC/LA,iBAAA;EACA,oBAAA;EDgMA,+BAAA;EACA,wBAAA;EACA,+BAAA;EACA,oBAAA;E/B63HD;A+Bz3HC;EACE,YAAA;E/B23HH;A+Bz4HD;EAmBI,gBAAA;EACA,aAAA;EACA,aAAA;EACA,oBAAA;E/By3HH;A+B/4HD;EAyBI,iBAAA;E/By3HH;A+Bn3HD;EAAA;IAFI,eAAA;I/By3HD;EACF;A+Bh3HD;EACE,qBAAA;E/Bk3HD;A+Bn3HD;EAII,mBAAA;EACA,sBAAA;EACA,mBAAA;E/Bk3HH;A+Bt1HC;EAAA;IAtBI,kBAAA;IACA,aAAA;IACA,aAAA;IACA,eAAA;IACA,+BAAA;IACA,WAAA;IACA,0BAAA;YAAA,kBAAA;I/Bg3HH;E+Bh2HD;;IAbM,4BAAA;I/Bi3HL;E+Bp2HD;IAVM,mBAAA;I/Bi3HL;E+Bh3HK;;IAEE,wBAAA;I/Bk3HP;EACF;A+Bh2HD;EAAA;IAXI,aAAA;IACA,WAAA;I/B+2HD;E+Br2HH;IAPM,aAAA;I/B+2HH;E+Bx2HH;IALQ,mBAAA;IACA,sBAAA;I/Bg3HL;EACF;A+Br2HD;EACE,oBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,sCAAA;E1B/NA,8FAAA;EACQ,sFAAA;E2B/DR,iBAAA;EACA,oBAAA;EhCuoID;AkB9pHD;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlBguHH;EkBpqHH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlB8tHH;EkBzqHH;IAhDM,uBAAA;IlB4tHH;EkB5qHH;IA5CM,uBAAA;IACA,wBAAA;IlB2tHH;EkBhrHH;;;IAtCQ,aAAA;IlB2tHL;EkBrrHH;IAhCM,aAAA;IlBwtHH;EkBxrHH;IA5BM,kBAAA;IACA,wBAAA;IlButHH;EkB5rHH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlBotHH;EkBnsHH;;IAdQ,iBAAA;IlBqtHL;EkBvsHH;;IATM,oBAAA;IACA,gBAAA;IlBotHH;EkB5sHH;IAHM,QAAA;IlBktHH;EACF;A+B94HC;EAAA;IANI,oBAAA;I/Bw5HH;E+Bt5HG;IACE,kBAAA;I/Bw5HL;EACF;A+Bv4HD;EAAA;IARI,aAAA;IACA,WAAA;IACA,gBAAA;IACA,iBAAA;IACA,gBAAA;IACA,mBAAA;I1B1PF,0BAAA;IACQ,kBAAA;IL8oIP;EACF;A+B74HD;EACE,eAAA;EHrUA,4BAAA;EACC,2BAAA;E5BqtIF;A+B74HD;EACE,kBAAA;EH1UA,8BAAA;EACC,6BAAA;EAOD,+BAAA;EACC,8BAAA;E5BotIF;A+Bz4HD;ECjVE,iBAAA;EACA,oBAAA;EhC6tID;A+B14HC;ECpVA,kBAAA;EACA,qBAAA;EhCiuID;A+B34HC;ECvVA,kBAAA;EACA,qBAAA;EhCquID;A+Br4HD;ECjWE,kBAAA;EACA,qBAAA;EhCyuID;A+Bj4HD;EAAA;IAJI,aAAA;IACA,mBAAA;IACA,oBAAA;I/By4HD;EACF;A+B52HD;EAhBE;IEzWA,wBAAA;IjCyuIC;E+B/3HD;IE7WA,yBAAA;IF+WE,qBAAA;I/Bi4HD;E+Bn4HD;IAKI,iBAAA;I/Bi4HH;EACF;A+Bx3HD;EACE,2BAAA;EACA,uBAAA;E/B03HD;A+B53HD;EAKI,gBAAA;E/B03HH;A+Bz3HG;;EAEE,gBAAA;EACA,+BAAA;E/B23HL;A+Bp4HD;EAcI,gBAAA;E/By3HH;A+Bv4HD;EAmBM,gBAAA;E/Bu3HL;A+Br3HK;;EAEE,gBAAA;EACA,+BAAA;E/Bu3HP;A+Bn3HK;;;EAGE,gBAAA;EACA,2BAAA;E/Bq3HP;A+Bj3HK;;;EAGE,gBAAA;EACA,+BAAA;E/Bm3HP;A+B35HD;EA8CI,uBAAA;E/Bg3HH;A+B/2HG;;EAEE,2BAAA;E/Bi3HL;A+Bl6HD;EAoDM,2BAAA;E/Bi3HL;A+Br6HD;;EA0DI,uBAAA;E/B+2HH;A+Bx2HK;;;EAGE,2BAAA;EACA,gBAAA;E/B02HP;A+Bz0HC;EAAA;IAzBQ,gBAAA;I/Bs2HP;E+Br2HO;;IAEE,gBAAA;IACA,+BAAA;I/Bu2HT;E+Bn2HO;;;IAGE,gBAAA;IACA,2BAAA;I/Bq2HT;E+Bj2HO;;;IAGE,gBAAA;IACA,+BAAA;I/Bm2HT;EACF;A+Br8HD;EA8GI,gBAAA;E/B01HH;A+Bz1HG;EACE,gBAAA;E/B21HL;A+B38HD;EAqHI,gBAAA;E/By1HH;A+Bx1HG;;EAEE,gBAAA;E/B01HL;A+Bt1HK;;;;EAEE,gBAAA;E/B01HP;A+Bl1HD;EACE,2BAAA;EACA,uBAAA;E/Bo1HD;A+Bt1HD;EAKI,gBAAA;E/Bo1HH;A+Bn1HG;;EAEE,gBAAA;EACA,+BAAA;E/Bq1HL;A+B91HD;EAcI,gBAAA;E/Bm1HH;A+Bj2HD;EAmBM,gBAAA;E/Bi1HL;A+B/0HK;;EAEE,gBAAA;EACA,+BAAA;E/Bi1HP;A+B70HK;;;EAGE,gBAAA;EACA,2BAAA;E/B+0HP;A+B30HK;;;EAGE,gBAAA;EACA,+BAAA;E/B60HP;A+Br3HD;EA+CI,uBAAA;E/By0HH;A+Bx0HG;;EAEE,2BAAA;E/B00HL;A+B53HD;EAqDM,2BAAA;E/B00HL;A+B/3HD;;EA2DI,uBAAA;E/Bw0HH;A+Bl0HK;;;EAGE,2BAAA;EACA,gBAAA;E/Bo0HP;A+B7xHC;EAAA;IA/BQ,uBAAA;I/Bg0HP;E+BjyHD;IA5BQ,2BAAA;I/Bg0HP;E+BpyHD;IAzBQ,gBAAA;I/Bg0HP;E+B/zHO;;IAEE,gBAAA;IACA,+BAAA;I/Bi0HT;E+B7zHO;;;IAGE,gBAAA;IACA,2BAAA;I/B+zHT;E+B3zHO;;;IAGE,gBAAA;IACA,+BAAA;I/B6zHT;EACF;A+Br6HD;EA+GI,gBAAA;E/ByzHH;A+BxzHG;EACE,gBAAA;E/B0zHL;A+B36HD;EAsHI,gBAAA;E/BwzHH;A+BvzHG;;EAEE,gBAAA;E/ByzHL;A+BrzHK;;;;EAEE,gBAAA;E/ByzHP;AkCp8ID;EACE,mBAAA;EACA,qBAAA;EACA,kBAAA;EACA,2BAAA;EACA,oBAAA;ElCs8ID;AkC38ID;EAQI,uBAAA;ElCs8IH;AkC98ID;EAWM,mBAAA;EACA,gBAAA;EACA,gBAAA;ElCs8IL;AkCn9ID;EAkBI,gBAAA;ElCo8IH;AmCx9ID;EACE,uBAAA;EACA,iBAAA;EACA,gBAAA;EACA,oBAAA;EnC09ID;AmC99ID;EAOI,iBAAA;EnC09IH;AmCj+ID;;EAUM,oBAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,mBAAA;EnC29IL;AmCz9IG;;EAGI,gBAAA;EPXN,gCAAA;EACG,6BAAA;E5Bs+IJ;AmCx9IG;;EPvBF,iCAAA;EACG,8BAAA;E5Bm/IJ;AmCn9IG;;;;EAEE,gBAAA;EACA,2BAAA;EACA,uBAAA;EnCu9IL;AmCj9IG;;;;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,iBAAA;EnCs9IL;AmC5gJD;;;;;;EAiEM,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,qBAAA;EnCm9IL;AmC18ID;;EC1EM,oBAAA;EACA,iBAAA;EpCwhJL;AoCthJG;;ERMF,gCAAA;EACG,6BAAA;E5BohJJ;AoCrhJG;;ERRF,iCAAA;EACG,8BAAA;E5BiiJJ;AmCp9ID;;EC/EM,mBAAA;EACA,iBAAA;EpCuiJL;AoCriJG;;ERMF,gCAAA;EACG,6BAAA;E5BmiJJ;AoCpiJG;;ERRF,iCAAA;EACG,8BAAA;E5BgjJJ;AqCnjJD;EACE,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oBAAA;ErCqjJD;AqCzjJD;EAOI,iBAAA;ErCqjJH;AqC5jJD;;EAUM,uBAAA;EACA,mBAAA;EACA,2BAAA;EACA,2BAAA;EACA,qBAAA;ErCsjJL;AqCpkJD;;EAmBM,uBAAA;EACA,2BAAA;ErCqjJL;AqCzkJD;;EA2BM,cAAA;ErCkjJL;AqC7kJD;;EAkCM,aAAA;ErC+iJL;AqCjlJD;;;;EA2CM,gBAAA;EACA,2BAAA;EACA,qBAAA;ErC4iJL;AsC1lJD;EACE,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,qBAAA;EACA,0BAAA;EACA,sBAAA;EtC4lJD;AsCxlJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EtC0lJL;AsCrlJC;EACE,eAAA;EtCulJH;AsCnlJC;EACE,oBAAA;EACA,WAAA;EtCqlJH;AsC9kJD;ECtCE,2BAAA;EvCunJD;AuCpnJG;;EAEE,2BAAA;EvCsnJL;AsCjlJD;EC1CE,2BAAA;EvC8nJD;AuC3nJG;;EAEE,2BAAA;EvC6nJL;AsCplJD;EC9CE,2BAAA;EvCqoJD;AuCloJG;;EAEE,2BAAA;EvCooJL;AsCvlJD;EClDE,2BAAA;EvC4oJD;AuCzoJG;;EAEE,2BAAA;EvC2oJL;AsC1lJD;ECtDE,2BAAA;EvCmpJD;AuChpJG;;EAEE,2BAAA;EvCkpJL;AsC7lJD;EC1DE,2BAAA;EvC0pJD;AuCvpJG;;EAEE,2BAAA;EvCypJL;AwC3pJD;EACE,uBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,0BAAA;EACA,qBAAA;EACA,oBAAA;EACA,2BAAA;EACA,qBAAA;ExC6pJD;AwC1pJC;EACE,eAAA;ExC4pJH;AwCxpJC;EACE,oBAAA;EACA,WAAA;ExC0pJH;AwCvpJC;EACE,QAAA;EACA,kBAAA;ExCypJH;AwCppJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;ExCspJL;AwCjpJC;;EAEE,gBAAA;EACA,2BAAA;ExCmpJH;AwChpJC;EACE,cAAA;ExCkpJH;AwC/oJC;EACE,mBAAA;ExCipJH;AwC9oJC;EACE,kBAAA;ExCgpJH;AyCzsJD;EACE,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,2BAAA;EzC2sJD;AyC/sJD;;EAQI,gBAAA;EzC2sJH;AyCntJD;EAYI,qBAAA;EACA,iBAAA;EACA,kBAAA;EzC0sJH;AyCxtJD;EAkBI,2BAAA;EzCysJH;AyCtsJC;;EAEE,oBAAA;EzCwsJH;AyC/tJD;EA2BI,iBAAA;EzCusJH;AyCtrJD;EAAA;IAbI,iBAAA;IzCusJD;EyCrsJC;;IAEE,oBAAA;IACA,qBAAA;IzCusJH;EyC/rJH;;IAHM,iBAAA;IzCssJH;EACF;A0C/uJD;EACE,gBAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;ErCiLA,6CAAA;EACK,wCAAA;EACG,qCAAA;ELikJT;A0C3vJD;;EAaI,mBAAA;EACA,oBAAA;E1CkvJH;A0C9uJC;;;EAGE,uBAAA;E1CgvJH;A0CrwJD;EA0BI,cAAA;EACA,gBAAA;E1C8uJH;A2CvwJD;EACE,eAAA;EACA,qBAAA;EACA,+BAAA;EACA,oBAAA;E3CywJD;A2C7wJD;EAQI,eAAA;EAEA,gBAAA;E3CuwJH;A2CjxJD;EAeI,mBAAA;E3CqwJH;A2CpxJD;;EAqBI,kBAAA;E3CmwJH;A2CxxJD;EAyBI,iBAAA;E3CkwJH;A2C1vJD;;EAEE,qBAAA;E3C4vJD;A2C9vJD;;EAMI,oBAAA;EACA,WAAA;EACA,cAAA;EACA,gBAAA;E3C4vJH;A2CpvJD;ECvDE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5C8yJD;A2CzvJD;EClDI,2BAAA;E5C8yJH;A2C5vJD;EC/CI,gBAAA;E5C8yJH;A2C3vJD;EC3DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5CyzJD;A2ChwJD;ECtDI,2BAAA;E5CyzJH;A2CnwJD;ECnDI,gBAAA;E5CyzJH;A2ClwJD;EC/DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5Co0JD;A2CvwJD;EC1DI,2BAAA;E5Co0JH;A2C1wJD;ECvDI,gBAAA;E5Co0JH;A2CzwJD;ECnEE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5C+0JD;A2C9wJD;EC9DI,2BAAA;E5C+0JH;A2CjxJD;EC3DI,gBAAA;E5C+0JH;A6Cj1JD;EACE;IAAQ,6BAAA;I7Co1JP;E6Cn1JD;IAAQ,0BAAA;I7Cs1JP;EACF;A6Cn1JD;EACE;IAAQ,6BAAA;I7Cs1JP;E6Cr1JD;IAAQ,0BAAA;I7Cw1JP;EACF;A6C31JD;EACE;IAAQ,6BAAA;I7Cs1JP;E6Cr1JD;IAAQ,0BAAA;I7Cw1JP;EACF;A6Cj1JD;EACE,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,2BAAA;EACA,oBAAA;ExCsCA,wDAAA;EACQ,gDAAA;EL8yJT;A6Ch1JD;EACE,aAAA;EACA,WAAA;EACA,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;ExCyBA,wDAAA;EACQ,gDAAA;EAyHR,qCAAA;EACK,gCAAA;EACG,6BAAA;ELksJT;A6C70JD;;ECCI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDAF,oCAAA;UAAA,4BAAA;E7Ci1JD;A6C10JD;;ExC5CE,4DAAA;EACK,uDAAA;EACG,oDAAA;EL03JT;A6Cv0JD;EErEE,2BAAA;E/C+4JD;A+C54JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9C+1JH;A6C30JD;EEzEE,2BAAA;E/Cu5JD;A+Cp5JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cu2JH;A6C/0JD;EE7EE,2BAAA;E/C+5JD;A+C55JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9C+2JH;A6Cn1JD;EEjFE,2BAAA;E/Cu6JD;A+Cp6JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cu3JH;AgD/6JD;EAEE,kBAAA;EhDg7JD;AgD96JC;EACE,eAAA;EhDg7JH;AgD56JD;;EAEE,SAAA;EACA,kBAAA;EhD86JD;AgD36JD;EACE,gBAAA;EhD66JD;AgD16JD;EACE,gBAAA;EhD46JD;AgDz6JD;;EAEE,oBAAA;EhD26JD;AgDx6JD;;EAEE,qBAAA;EhD06JD;AgDv6JD;;;EAGE,qBAAA;EACA,qBAAA;EhDy6JD;AgDt6JD;EACE,wBAAA;EhDw6JD;AgDr6JD;EACE,wBAAA;EhDu6JD;AgDn6JD;EACE,eAAA;EACA,oBAAA;EhDq6JD;AgD/5JD;EACE,iBAAA;EACA,kBAAA;EhDi6JD;AiDn9JD;EAEE,qBAAA;EACA,iBAAA;EjDo9JD;AiD58JD;EACE,oBAAA;EACA,gBAAA;EACA,oBAAA;EAEA,qBAAA;EACA,2BAAA;EACA,2BAAA;EjD68JD;AiD18JC;ErB3BA,8BAAA;EACC,6BAAA;E5Bw+JF;AiD38JC;EACE,kBAAA;ErBvBF,iCAAA;EACC,gCAAA;E5Bq+JF;AiDp8JD;EACE,gBAAA;EjDs8JD;AiDv8JD;EAII,gBAAA;EjDs8JH;AiDl8JC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;EjDo8JH;AiD97JC;;;EAGE,2BAAA;EACA,gBAAA;EACA,qBAAA;EjDg8JH;AiDr8JC;;;EASI,gBAAA;EjDi8JL;AiD18JC;;;EAYI,gBAAA;EjDm8JL;AiD97JC;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EjDg8JH;AiDt8JC;;;;;;;;;EAYI,gBAAA;EjDq8JL;AiDj9JC;;;EAeI,gBAAA;EjDu8JL;AkDniKC;EACE,gBAAA;EACA,2BAAA;ElDqiKH;AkDniKG;EACE,gBAAA;ElDqiKL;AkDtiKG;EAII,gBAAA;ElDqiKP;AkDliKK;;EAEE,gBAAA;EACA,2BAAA;ElDoiKP;AkDliKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDoiKP;AkDzjKC;EACE,gBAAA;EACA,2BAAA;ElD2jKH;AkDzjKG;EACE,gBAAA;ElD2jKL;AkD5jKG;EAII,gBAAA;ElD2jKP;AkDxjKK;;EAEE,gBAAA;EACA,2BAAA;ElD0jKP;AkDxjKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElD0jKP;AkD/kKC;EACE,gBAAA;EACA,2BAAA;ElDilKH;AkD/kKG;EACE,gBAAA;ElDilKL;AkDllKG;EAII,gBAAA;ElDilKP;AkD9kKK;;EAEE,gBAAA;EACA,2BAAA;ElDglKP;AkD9kKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDglKP;AkDrmKC;EACE,gBAAA;EACA,2BAAA;ElDumKH;AkDrmKG;EACE,gBAAA;ElDumKL;AkDxmKG;EAII,gBAAA;ElDumKP;AkDpmKK;;EAEE,gBAAA;EACA,2BAAA;ElDsmKP;AkDpmKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDsmKP;AiD1gKD;EACE,eAAA;EACA,oBAAA;EjD4gKD;AiD1gKD;EACE,kBAAA;EACA,kBAAA;EjD4gKD;AmDhoKD;EACE,qBAAA;EACA,2BAAA;EACA,+BAAA;EACA,oBAAA;E9C0DA,mDAAA;EACQ,2CAAA;ELykKT;AmD/nKD;EACE,eAAA;EnDioKD;AmD5nKD;EACE,oBAAA;EACA,sCAAA;EvBpBA,8BAAA;EACC,6BAAA;E5BmpKF;AmDloKD;EAMI,gBAAA;EnD+nKH;AmD1nKD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,gBAAA;EnD4nKD;AmDhoKD;;;;;EAWI,gBAAA;EnD4nKH;AmDvnKD;EACE,oBAAA;EACA,2BAAA;EACA,+BAAA;EvBxCA,iCAAA;EACC,gCAAA;E5BkqKF;AmDjnKD;;EAGI,kBAAA;EnDknKH;AmDrnKD;;EAMM,qBAAA;EACA,kBAAA;EnDmnKL;AmD/mKG;;EAEI,eAAA;EvBvEN,8BAAA;EACC,6BAAA;E5ByrKF;AmD9mKG;;EAEI,kBAAA;EvBtEN,iCAAA;EACC,gCAAA;E5BurKF;AmD3mKD;EAEI,qBAAA;EnD4mKH;AmDzmKD;EACE,qBAAA;EnD2mKD;AmDnmKD;;;EAII,kBAAA;EnDomKH;AmDxmKD;;;EAOM,oBAAA;EACA,qBAAA;EnDsmKL;AmD9mKD;;EvBnGE,8BAAA;EACC,6BAAA;E5BqtKF;AmDnnKD;;;;EAmBQ,6BAAA;EACA,8BAAA;EnDsmKP;AmD1nKD;;;;;;;;EAwBU,6BAAA;EnD4mKT;AmDpoKD;;;;;;;;EA4BU,8BAAA;EnDknKT;AmD9oKD;;EvB3FE,iCAAA;EACC,gCAAA;E5B6uKF;AmDnpKD;;;;EAyCQ,gCAAA;EACA,iCAAA;EnDgnKP;AmD1pKD;;;;;;;;EA8CU,gCAAA;EnDsnKT;AmDpqKD;;;;;;;;EAkDU,iCAAA;EnD4nKT;AmD9qKD;;;;EA2DI,+BAAA;EnDynKH;AmDprKD;;EA+DI,eAAA;EnDynKH;AmDxrKD;;EAmEI,WAAA;EnDynKH;AmD5rKD;;;;;;;;;;;;EA0EU,gBAAA;EnDgoKT;AmD1sKD;;;;;;;;;;;;EA8EU,iBAAA;EnD0oKT;AmDxtKD;;;;;;;;EAuFU,kBAAA;EnD2oKT;AmDluKD;;;;;;;;EAgGU,kBAAA;EnD4oKT;AmD5uKD;EAsGI,WAAA;EACA,kBAAA;EnDyoKH;AmD/nKD;EACE,qBAAA;EnDioKD;AmDloKD;EAKI,kBAAA;EACA,oBAAA;EnDgoKH;AmDtoKD;EASM,iBAAA;EnDgoKL;AmDzoKD;EAcI,kBAAA;EnD8nKH;AmD5oKD;;EAkBM,+BAAA;EnD8nKL;AmDhpKD;EAuBI,eAAA;EnD4nKH;AmDnpKD;EAyBM,kCAAA;EnD6nKL;AmDtnKD;ECpPE,uBAAA;EpD62KD;AoD32KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpD62KH;AoDh3KC;EAMI,2BAAA;EpD62KL;AoDn3KC;EASI,gBAAA;EACA,2BAAA;EpD62KL;AoD12KC;EAEI,8BAAA;EpD22KL;AmDroKD;ECvPE,uBAAA;EpD+3KD;AoD73KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpD+3KH;AoDl4KC;EAMI,2BAAA;EpD+3KL;AoDr4KC;EASI,gBAAA;EACA,2BAAA;EpD+3KL;AoD53KC;EAEI,8BAAA;EpD63KL;AmDppKD;EC1PE,uBAAA;EpDi5KD;AoD/4KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDi5KH;AoDp5KC;EAMI,2BAAA;EpDi5KL;AoDv5KC;EASI,gBAAA;EACA,2BAAA;EpDi5KL;AoD94KC;EAEI,8BAAA;EpD+4KL;AmDnqKD;EC7PE,uBAAA;EpDm6KD;AoDj6KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDm6KH;AoDt6KC;EAMI,2BAAA;EpDm6KL;AoDz6KC;EASI,gBAAA;EACA,2BAAA;EpDm6KL;AoDh6KC;EAEI,8BAAA;EpDi6KL;AmDlrKD;EChQE,uBAAA;EpDq7KD;AoDn7KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDq7KH;AoDx7KC;EAMI,2BAAA;EpDq7KL;AoD37KC;EASI,gBAAA;EACA,2BAAA;EpDq7KL;AoDl7KC;EAEI,8BAAA;EpDm7KL;AmDjsKD;ECnQE,uBAAA;EpDu8KD;AoDr8KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDu8KH;AoD18KC;EAMI,2BAAA;EpDu8KL;AoD78KC;EASI,gBAAA;EACA,2BAAA;EpDu8KL;AoDp8KC;EAEI,8BAAA;EpDq8KL;AqDr9KD;EACE,oBAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;ErDu9KD;AqD59KD;;;;;EAYI,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,cAAA;EACA,aAAA;EACA,WAAA;ErDu9KH;AqDn9KC;EACE,wBAAA;ErDq9KH;AqDj9KC;EACE,qBAAA;ErDm9KH;AsD7+KD;EACE,kBAAA;EACA,eAAA;EACA,qBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EjDwDA,yDAAA;EACQ,iDAAA;ELw7KT;AsDv/KD;EASI,oBAAA;EACA,mCAAA;EtDi/KH;AsD5+KD;EACE,eAAA;EACA,oBAAA;EtD8+KD;AsD5+KD;EACE,cAAA;EACA,oBAAA;EtD8+KD;AuDpgLD;EACE,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,8BAAA;EjCRA,cAAA;EAGA,2BAAA;EtB6gLD;AuDrgLC;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EjCfF,cAAA;EAGA,2BAAA;EtBqhLD;AuDjgLC;EACE,YAAA;EACA,iBAAA;EACA,yBAAA;EACA,WAAA;EACA,0BAAA;EvDmgLH;AwDxhLD;EACE,kBAAA;ExD0hLD;AwDthLD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,mCAAA;EAIA,YAAA;ExDqhLD;AwDlhLC;EnD+GA,uCAAA;EACI,mCAAA;EACC,kCAAA;EACG,+BAAA;EAkER,qDAAA;EAEK,2CAAA;EACG,qCAAA;ELq2KT;AwDxhLC;EnD2GA,oCAAA;EACI,gCAAA;EACC,+BAAA;EACG,4BAAA;ELg7KT;AwD5hLD;EACE,oBAAA;EACA,kBAAA;ExD8hLD;AwD1hLD;EACE,oBAAA;EACA,aAAA;EACA,cAAA;ExD4hLD;AwDxhLD;EACE,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;EnDaA,kDAAA;EACQ,0CAAA;EmDZR,sCAAA;UAAA,8BAAA;EAEA,YAAA;ExD0hLD;AwDthLD;EACE,oBAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,2BAAA;ExDwhLD;AwDthLC;ElCnEA,YAAA;EAGA,0BAAA;EtB0lLD;AwDzhLC;ElCpEA,cAAA;EAGA,2BAAA;EtB8lLD;AwDxhLD;EACE,eAAA;EACA,kCAAA;EACA,2BAAA;ExD0hLD;AwDvhLD;EACE,kBAAA;ExDyhLD;AwDrhLD;EACE,WAAA;EACA,yBAAA;ExDuhLD;AwDlhLD;EACE,oBAAA;EACA,eAAA;ExDohLD;AwDhhLD;EACE,eAAA;EACA,mBAAA;EACA,+BAAA;ExDkhLD;AwDrhLD;EAQI,kBAAA;EACA,kBAAA;ExDghLH;AwDzhLD;EAaI,mBAAA;ExD+gLH;AwD5hLD;EAiBI,gBAAA;ExD8gLH;AwDzgLD;EACE,oBAAA;EACA,cAAA;EACA,aAAA;EACA,cAAA;EACA,kBAAA;ExD2gLD;AwDz/KD;EAZE;IACE,cAAA;IACA,mBAAA;IxDwgLD;EwDtgLD;InDrEA,mDAAA;IACQ,2CAAA;IL8kLP;EwDrgLD;IAAY,cAAA;IxDwgLX;EACF;AwDngLD;EAFE;IAAY,cAAA;IxDygLX;EACF;AyDtpLD;EACE,oBAAA;EACA,eAAA;EACA,gBAAA;EACA,qBAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,kBAAA;EnCZA,YAAA;EAGA,0BAAA;EtBkqLD;AyDtpLC;EnCfA,cAAA;EAGA,2BAAA;EtBsqLD;AyDzpLC;EAAW,kBAAA;EAAmB,gBAAA;EzD6pL/B;AyD5pLC;EAAW,kBAAA;EAAmB,gBAAA;EzDgqL/B;AyD/pLC;EAAW,iBAAA;EAAmB,gBAAA;EzDmqL/B;AyDlqLC;EAAW,mBAAA;EAAmB,gBAAA;EzDsqL/B;AyDlqLD;EACE,kBAAA;EACA,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,uBAAA;EACA,2BAAA;EACA,oBAAA;EzDoqLD;AyDhqLD;EACE,oBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;EzDkqLD;AyD9pLC;EACE,WAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,WAAA;EACA,YAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,WAAA;EACA,WAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,UAAA;EACA,SAAA;EACA,kBAAA;EACA,6BAAA;EACA,6BAAA;EzDgqLH;AyD9pLC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,6BAAA;EACA,4BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,YAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;A0D/vLD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,kBAAA;EACA,cAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,yBAAA;EACA,kBAAA;EACA,2BAAA;EACA,sCAAA;UAAA,8BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;ErD6CA,mDAAA;EACQ,2CAAA;EqD1CR,qBAAA;E1D+vLD;A0D5vLC;EAAY,mBAAA;E1D+vLb;A0D9vLC;EAAY,mBAAA;E1DiwLb;A0DhwLC;EAAY,kBAAA;E1DmwLb;A0DlwLC;EAAY,oBAAA;E1DqwLb;A0DlwLD;EACE,WAAA;EACA,mBAAA;EACA,iBAAA;EACA,2BAAA;EACA,kCAAA;EACA,4BAAA;E1DowLD;A0DjwLD;EACE,mBAAA;E1DmwLD;A0D3vLC;;EAEE,oBAAA;EACA,gBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;E1D6vLH;A0D1vLD;EACE,oBAAA;E1D4vLD;A0D1vLD;EACE,oBAAA;EACA,aAAA;E1D4vLD;A0DxvLC;EACE,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;EACA,uCAAA;EACA,eAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;E1D2vLL;A0DxvLC;EACE,UAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,6BAAA;EACA,yCAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,WAAA;EACA,eAAA;EACA,sBAAA;EACA,6BAAA;E1D2vLL;A0DxvLC;EACE,WAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;EACA,0CAAA;EACA,YAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,UAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;E1D2vLL;A0DvvLC;EACE,UAAA;EACA,cAAA;EACA,mBAAA;EACA,uBAAA;EACA,4BAAA;EACA,wCAAA;E1DyvLH;A0DxvLG;EACE,cAAA;EACA,YAAA;EACA,uBAAA;EACA,4BAAA;EACA,eAAA;E1D0vLL;A2Dv3LD;EACE,oBAAA;E3Dy3LD;A2Dt3LD;EACE,oBAAA;EACA,kBAAA;EACA,aAAA;E3Dw3LD;A2D33LD;EAMI,eAAA;EACA,oBAAA;EtD6KF,2CAAA;EACK,sCAAA;EACG,mCAAA;EL4sLT;A2Dl4LD;;EAcM,gBAAA;E3Dw3LL;A2D91LC;EAAA;ItDiKA,wDAAA;IAEK,8CAAA;IACG,wCAAA;IA7JR,qCAAA;IAEQ,6BAAA;IA+GR,2BAAA;IAEQ,mBAAA;ILivLP;E2D53LG;;ItDmHJ,4CAAA;IACQ,oCAAA;IsDjHF,SAAA;I3D+3LL;E2D73LG;;ItD8GJ,6CAAA;IACQ,qCAAA;IsD5GF,SAAA;I3Dg4LL;E2D93LG;;;ItDyGJ,yCAAA;IACQ,iCAAA;IsDtGF,SAAA;I3Di4LL;EACF;A2Dv6LD;;;EA6CI,gBAAA;E3D+3LH;A2D56LD;EAiDI,SAAA;E3D83LH;A2D/6LD;;EAsDI,oBAAA;EACA,QAAA;EACA,aAAA;E3D63LH;A2Dr7LD;EA4DI,YAAA;E3D43LH;A2Dx7LD;EA+DI,aAAA;E3D43LH;A2D37LD;;EAmEI,SAAA;E3D43LH;A2D/7LD;EAuEI,aAAA;E3D23LH;A2Dl8LD;EA0EI,YAAA;E3D23LH;A2Dn3LD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,YAAA;ErC9FA,cAAA;EAGA,2BAAA;EqC6FA,iBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3Ds3LD;A2Dj3LC;EblGE,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9Cs9LH;A2Dr3LC;EACE,YAAA;EACA,UAAA;EbvGA,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9C+9LH;A2Dv3LC;;EAEE,YAAA;EACA,gBAAA;EACA,uBAAA;ErCtHF,cAAA;EAGA,2BAAA;EtB8+LD;A2Dx5LD;;;;EAsCI,oBAAA;EACA,UAAA;EACA,YAAA;EACA,uBAAA;E3Dw3LH;A2Dj6LD;;EA6CI,WAAA;EACA,oBAAA;E3Dw3LH;A2Dt6LD;;EAkDI,YAAA;EACA,qBAAA;E3Dw3LH;A2D36LD;;EAuDI,aAAA;EACA,cAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;E3Dw3LH;A2Dn3LG;EACE,kBAAA;E3Dq3LL;A2Dj3LG;EACE,kBAAA;E3Dm3LL;A2Dz2LD;EACE,oBAAA;EACA,cAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;E3D22LD;A2Dp3LD;EAYI,uBAAA;EACA,aAAA;EACA,cAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;EACA,qBAAA;EACA,iBAAA;EAWA,2BAAA;EACA,oCAAA;E3Di2LH;A2Dh4LD;EAkCI,WAAA;EACA,aAAA;EACA,cAAA;EACA,2BAAA;E3Di2LH;A2D11LD;EACE,oBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3D41LD;A2D31LC;EACE,mBAAA;E3D61LH;A2DpzLD;EAhCE;;;;IAKI,aAAA;IACA,cAAA;IACA,mBAAA;IACA,iBAAA;I3Ds1LH;E2D91LD;;IAYI,oBAAA;I3Ds1LH;E2Dl2LD;;IAgBI,qBAAA;I3Ds1LH;E2Dj1LD;IACE,WAAA;IACA,YAAA;IACA,sBAAA;I3Dm1LD;E2D/0LD;IACE,cAAA;I3Di1LD;EACF;A4D/kMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,cAAA;EACA,gBAAA;E5D6mMH;A4D3mMC;;;;;;;;;;;;;;;EACE,aAAA;E5D2nMH;AiCnoMD;E4BRE,gBAAA;EACA,mBAAA;EACA,oBAAA;E7D8oMD;AiCroMD;EACE,yBAAA;EjCuoMD;AiCroMD;EACE,wBAAA;EjCuoMD;AiC/nMD;EACE,0BAAA;EjCioMD;AiC/nMD;EACE,2BAAA;EjCioMD;AiC/nMD;EACE,oBAAA;EjCioMD;AiC/nMD;E6BzBE,aAAA;EACA,oBAAA;EACA,mBAAA;EACA,+BAAA;EACA,WAAA;E9D2pMD;AiC7nMD;EACE,0BAAA;EACA,+BAAA;EjC+nMD;AiCxnMD;EACE,iBAAA;EjC0nMD;A+D5pMD;EACE,qBAAA;E/D8pMD;A+DxpMD;;;;ECdE,0BAAA;EhE4qMD;A+DvpMD;;;;;;;;;;;;EAYE,0BAAA;E/DypMD;A+DlpMD;EAAA;IChDE,2BAAA;IhEssMC;EgErsMD;IAAU,gBAAA;IhEwsMT;EgEvsMD;IAAU,+BAAA;IhE0sMT;EgEzsMD;;IACU,gCAAA;IhE4sMT;EACF;A+D5pMD;EAAA;IAFI,2BAAA;I/DkqMD;EACF;A+D5pMD;EAAA;IAFI,4BAAA;I/DkqMD;EACF;A+D5pMD;EAAA;IAFI,kCAAA;I/DkqMD;EACF;A+D3pMD;EAAA;ICrEE,2BAAA;IhEouMC;EgEnuMD;IAAU,gBAAA;IhEsuMT;EgEruMD;IAAU,+BAAA;IhEwuMT;EgEvuMD;;IACU,gCAAA;IhE0uMT;EACF;A+DrqMD;EAAA;IAFI,2BAAA;I/D2qMD;EACF;A+DrqMD;EAAA;IAFI,4BAAA;I/D2qMD;EACF;A+DrqMD;EAAA;IAFI,kCAAA;I/D2qMD;EACF;A+DpqMD;EAAA;IC1FE,2BAAA;IhEkwMC;EgEjwMD;IAAU,gBAAA;IhEowMT;EgEnwMD;IAAU,+BAAA;IhEswMT;EgErwMD;;IACU,gCAAA;IhEwwMT;EACF;A+D9qMD;EAAA;IAFI,2BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,4BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,kCAAA;I/DorMD;EACF;A+D7qMD;EAAA;IC/GE,2BAAA;IhEgyMC;EgE/xMD;IAAU,gBAAA;IhEkyMT;EgEjyMD;IAAU,+BAAA;IhEoyMT;EgEnyMD;;IACU,gCAAA;IhEsyMT;EACF;A+DvrMD;EAAA;IAFI,2BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,4BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,kCAAA;I/D6rMD;EACF;A+DtrMD;EAAA;IC5HE,0BAAA;IhEszMC;EACF;A+DtrMD;EAAA;ICjIE,0BAAA;IhE2zMC;EACF;A+DtrMD;EAAA;ICtIE,0BAAA;IhEg0MC;EACF;A+DtrMD;EAAA;IC3IE,0BAAA;IhEq0MC;EACF;A+DnrMD;ECnJE,0BAAA;EhEy0MD;A+DhrMD;EAAA;ICjKE,2BAAA;IhEq1MC;EgEp1MD;IAAU,gBAAA;IhEu1MT;EgEt1MD;IAAU,+BAAA;IhEy1MT;EgEx1MD;;IACU,gCAAA;IhE21MT;EACF;A+D9rMD;EACE,0BAAA;E/DgsMD;A+D3rMD;EAAA;IAFI,2BAAA;I/DisMD;EACF;A+D/rMD;EACE,0BAAA;E/DisMD;A+D5rMD;EAAA;IAFI,4BAAA;I/DksMD;EACF;A+DhsMD;EACE,0BAAA;E/DksMD;A+D7rMD;EAAA;IAFI,kCAAA;I/DmsMD;EACF;A+D5rMD;EAAA;ICpLE,0BAAA;IhEo3MC;EACF","file":"bootstrap.css","sourcesContent":["/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n select {\n background: #fff !important;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\2a\";\n}\n.glyphicon-plus:before {\n content: \"\\2b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #ffffff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #ffffff;\n background-color: #333333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #dddddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #dddddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #dddddd;\n}\n.table .table {\n background-color: #ffffff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #dddddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #ffffff;\n background-image: none;\n border: 1px solid #cccccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999999;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n background-color: #eeeeee;\n opacity: 1;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.form-group-sm .form-control {\n height: 30px;\n line-height: 30px;\n}\ntextarea.form-group-sm .form-control,\nselect[multiple].form-group-sm .form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.form-group-lg .form-control {\n height: 46px;\n line-height: 46px;\n}\ntextarea.form-group-lg .form-control,\nselect[multiple].form-group-lg .form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 14.333333px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n pointer-events: none;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default {\n color: #333333;\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default.focus,\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default .badge {\n color: #ffffff;\n background-color: #333333;\n}\n.btn-primary {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus,\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #ffffff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.btn-success {\n color: #ffffff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus,\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #ffffff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #ffffff;\n}\n.btn-info {\n color: #ffffff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus,\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #ffffff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #ffffff;\n}\n.btn-warning {\n color: #ffffff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus,\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #ffffff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #ffffff;\n}\n.btn-danger {\n color: #ffffff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus,\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #ffffff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #ffffff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n visibility: hidden;\n}\n.collapse.in {\n display: block;\n visibility: visible;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px solid;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #ffffff;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #ffffff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px solid;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-bottom-left-radius: 4px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #dddddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #dddddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #ffffff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n visibility: hidden;\n}\n.tab-content > .active {\n display: block;\n visibility: visible;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n visibility: visible !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #dddddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #dddddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777777;\n}\n.navbar-default .navbar-link:hover {\n color: #333333;\n}\n.navbar-default .btn-link {\n color: #777777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #cccccc;\n}\n.navbar-inverse {\n background-color: #222222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #ffffff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #ffffff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #ffffff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #ffffff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #cccccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n color: #23527c;\n background-color: #eeeeee;\n border-color: #dddddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #ffffff;\n border-color: #dddddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #ffffff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #ffffff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #ffffff;\n line-height: 1;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding: 30px 15px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding: 48px 0;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #ffffff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item {\n color: #555555;\n}\na.list-group-item .list-group-item-heading {\n color: #333333;\n}\na.list-group-item:hover,\na.list-group-item:focus {\n text-decoration: none;\n color: #555555;\n background-color: #f5f5f5;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\na.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\na.list-group-item-success.active:hover,\na.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\na.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\na.list-group-item-info.active:hover,\na.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\na.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\na.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #ffffff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #dddddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #dddddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #dddddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #dddddd;\n}\n.panel-default {\n border-color: #dddddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #dddddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #dddddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #dddddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000000;\n text-shadow: 0 1px 0 #ffffff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #ffffff;\n border: 1px solid #999999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n background-color: #000000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n min-height: 16.42857143px;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n visibility: visible;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 12px;\n font-weight: normal;\n line-height: 1.4;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #ffffff;\n text-align: center;\n text-decoration: none;\n background-color: #000000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n font-weight: normal;\n line-height: 1.42857143;\n text-align: left;\n background-color: #ffffff;\n background-clip: padding-box;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n white-space: normal;\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #ffffff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #ffffff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #ffffff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #ffffff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000;\n -moz-perspective: 1000;\n perspective: 1000;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #ffffff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #ffffff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #ffffff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -15px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -15px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n visibility: hidden !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n// user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n// (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box; // 2\n box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n //\n // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245\n // Once fixed, we can just straight up remove this.\n select {\n background: #fff !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// <a href=\"#\"><span class=\"glyphicon glyphicon-star\"></span> Star</a>\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\2a\"; } }\n.glyphicon-plus { &:before { content: \"\\2b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @grid-float-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &[disabled],\n &[readonly],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n background-color: @input-bg-disabled;\n opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655\n }\n\n // Reset height for `textarea`s\n textarea& {\n height: auto;\n }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n\n\n// Special styles for iOS temporal inputs\n//\n// In Mobile Safari, setting `display: block` on temporal inputs causes the\n// text within the input to become vertically misaligned. As a workaround, we\n// set a pixel line-height that matches the given height of the input, but only\n// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848\n\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: @input-height-base;\n\n &.input-sm,\n .input-group-sm & {\n line-height: @input-height-small;\n }\n\n &.input-lg,\n .input-group-lg & {\n line-height: @input-height-large;\n }\n }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n\n label {\n min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n// Some special care is needed because <label>s don't inherit their parent's `cursor`.\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n &[disabled],\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used directly on <label>s\n.radio-inline,\n.checkbox-inline {\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used on elements with <label> descendants\n.radio,\n.checkbox {\n &.disabled,\n fieldset[disabled] & {\n label {\n cursor: @cursor-disabled;\n }\n }\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n // Size it appropriately next to real form controls\n padding-top: (@padding-base-vertical + 1);\n padding-bottom: (@padding-base-vertical + 1);\n // Remove default margin from `p`\n margin-bottom: 0;\n\n &.input-lg,\n &.input-sm {\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n//\n// The `.form-group-* form-control` variations are sadly duplicated to avoid the\n// issue documented in https://github.com/twbs/bootstrap/issues/15074.\n\n.input-sm {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n}\n.form-group-sm {\n .form-control {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n }\n .form-control-static {\n height: @input-height-small;\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n line-height: @line-height-small;\n }\n}\n\n.input-lg {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n}\n.form-group-lg {\n .form-control {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n }\n .form-control-static {\n height: @input-height-large;\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-large;\n }\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n.has-feedback {\n // Enable absolute positioning\n position: relative;\n\n // Ensure icons don't overlap text\n .form-control {\n padding-right: (@input-height-base * 1.25);\n }\n}\n// Feedback icon (requires .glyphicon classes)\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2; // Ensure icon is above input groups\n display: block;\n width: @input-height-base;\n height: @input-height-base;\n line-height: @input-height-base;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: @input-height-large;\n height: @input-height-large;\n line-height: @input-height-large;\n}\n.input-sm + .form-control-feedback {\n width: @input-height-small;\n height: @input-height-small;\n line-height: @input-height-small;\n}\n\n// Feedback states\n.has-success {\n .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n.has-warning {\n .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n.has-error {\n .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n\n// Reposition feedback icon if input has visible label above\n.has-feedback label {\n\n & ~ .form-control-feedback {\n top: (@line-height-computed + 5); // Height of the `label` and its margin\n }\n &.sr-only ~ .form-control-feedback {\n top: 0;\n }\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n display: block; // account for any element using help-block\n margin-top: 5px;\n margin-bottom: 10px;\n color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n // Kick in the inline\n @media (min-width: @screen-sm-min) {\n // Inline-block all the things for \"inline\"\n .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // In navbar-form, allow folks to *not* use `.form-group`\n .form-control {\n display: inline-block;\n width: auto; // Prevent labels from stacking above inputs in `.form-group`\n vertical-align: middle;\n }\n\n // Make static controls behave like regular ones\n .form-control-static {\n display: inline-block;\n }\n\n .input-group {\n display: inline-table;\n vertical-align: middle;\n\n .input-group-addon,\n .input-group-btn,\n .form-control {\n width: auto;\n }\n }\n\n // Input groups need that 100% width though\n .input-group > .form-control {\n width: 100%;\n }\n\n .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // Remove default margin on radios/checkboxes that were used for stacking, and\n // then undo the floating of radios and checkboxes to match.\n .radio,\n .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n\n label {\n padding-left: 0;\n }\n }\n .radio input[type=\"radio\"],\n .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n\n // Re-override the feedback icon.\n .has-feedback .form-control-feedback {\n top: 0;\n }\n }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n // Consistent vertical alignment of radios and checkboxes\n //\n // Labels also get some reset styles, but that is scoped to a media query below.\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n // Account for padding we're adding to ensure the alignment and of help text\n // and other content below items\n .radio,\n .checkbox {\n min-height: (@line-height-computed + (@padding-base-vertical + 1));\n }\n\n // Make form groups behave like rows\n .form-group {\n .make-row();\n }\n\n // Reset spacing and right align labels, but scope to media queries so that\n // labels on narrow viewports stack the same as a default form example.\n @media (min-width: @screen-sm-min) {\n .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n }\n\n // Validation states\n //\n // Reposition the icon because it's now within a grid column and columns have\n // `position: relative;` on them. Also accounts for the grid gutter padding.\n .has-feedback .form-control-feedback {\n right: (@grid-gutter-width / 2);\n }\n\n // Form group sizes\n //\n // Quick utility class for applying `.input-lg` and `.input-sm` styles to the\n // inputs and labels within a `.form-group`.\n .form-group-lg {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: ((@padding-large-vertical * @line-height-large) + 1);\n }\n }\n }\n .form-group-sm {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: (@padding-small-vertical + 1);\n }\n }\n }\n}\n","// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline,\n &.radio label,\n &.checkbox label,\n &.radio-inline label,\n &.checkbox-inline label {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-border-focus` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `<select>`\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n pointer-events: none; // Future-proof disabling of clicks\n .opacity(.65);\n .box-shadow(none);\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n visibility: hidden;\n\n &.in { display: block; visibility: visible; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base solid;\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base solid;\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n border-top-right-radius: @border-radius-base;\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n border-bottom-left-radius: @border-radius-base;\n .border-top-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @border-radius-base;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n visibility: hidden;\n }\n > .active {\n display: block;\n visibility: visible;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n visibility: visible !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n visibility: hidden !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n \n .btn-xs & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n \n .list-group-item > & {\n float: right;\n }\n \n .list-group-item > & + & {\n margin-right: 5px;\n }\n \n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding: @jumbotron-padding (@jumbotron-padding / 2);\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n \n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding: (@jumbotron-padding * 1.6) 0;\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: (@font-size-base * 4.5);\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n \n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n \n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n}\n\n\n// Linked list items\n//\n// Use anchor elements instead of `li`s or `div`s to create linked list items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n color: @list-group-link-hover-color;\n background-color: @list-group-hover-bg;\n }\n}\n\n.list-group-item {\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n background-color: @list-group-disabled-bg;\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-left: @panel-body-padding;\n padding-right: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n border-bottom-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsable panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n }\n\n // Modifier class for 16:9 aspect ratio\n &.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n }\n\n // Modifier class for 4:3 aspect ratio\n &.embed-responsive-4by3 {\n padding-bottom: 75%;\n }\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n min-height: (@modal-title-padding + @modal-title-line-height);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n visibility: visible;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-small;\n font-weight: normal;\n line-height: 1.4;\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n text-decoration: none;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: @line-height-base;\n text-align: left;\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Overrides for proper insertion\n white-space: normal;\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~'0.6s ease-in-out');\n .backface-visibility(~'hidden');\n .perspective(1000);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: 0;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n }\n\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: -15px;\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: -15px;\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n","// CSS image replacement\n//\n// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (will be removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#support-ie10-width\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// Responsive utilities\n\n//\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css
new file mode 100644
index 000000000..28f154dec
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 000000000..b93a4953f
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg
new file mode 100644
index 000000000..94fb5490a
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1,288 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
+<font-face units-per-em="1200" ascent="960" descent="-240" />
+<missing-glyph horiz-adv-x="500" />
+<glyph horiz-adv-x="0" />
+<glyph horiz-adv-x="400" />
+<glyph unicode=" " />
+<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" />
+<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xa0;" />
+<glyph unicode="&#xa5;" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" />
+<glyph unicode="&#x2000;" horiz-adv-x="650" />
+<glyph unicode="&#x2001;" horiz-adv-x="1300" />
+<glyph unicode="&#x2002;" horiz-adv-x="650" />
+<glyph unicode="&#x2003;" horiz-adv-x="1300" />
+<glyph unicode="&#x2004;" horiz-adv-x="433" />
+<glyph unicode="&#x2005;" horiz-adv-x="325" />
+<glyph unicode="&#x2006;" horiz-adv-x="216" />
+<glyph unicode="&#x2007;" horiz-adv-x="216" />
+<glyph unicode="&#x2008;" horiz-adv-x="162" />
+<glyph unicode="&#x2009;" horiz-adv-x="260" />
+<glyph unicode="&#x200a;" horiz-adv-x="72" />
+<glyph unicode="&#x202f;" horiz-adv-x="260" />
+<glyph unicode="&#x205f;" horiz-adv-x="325" />
+<glyph unicode="&#x20ac;" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" />
+<glyph unicode="&#x20bd;" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" />
+<glyph unicode="&#x2212;" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#x231b;" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" />
+<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#x2601;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" />
+<glyph unicode="&#x26fa;" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " />
+<glyph unicode="&#x2709;" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" />
+<glyph unicode="&#x270f;" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" />
+<glyph unicode="&#xe001;" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" />
+<glyph unicode="&#xe002;" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" />
+<glyph unicode="&#xe003;" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" />
+<glyph unicode="&#xe005;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" />
+<glyph unicode="&#xe006;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" />
+<glyph unicode="&#xe007;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" />
+<glyph unicode="&#xe008;" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" />
+<glyph unicode="&#xe009;" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" />
+<glyph unicode="&#xe010;" d="M50 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe011;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5 t35.5 14.5z" />
+<glyph unicode="&#xe012;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe013;" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" />
+<glyph unicode="&#xe014;" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" />
+<glyph unicode="&#xe015;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe016;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe017;" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" />
+<glyph unicode="&#xe018;" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe019;" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" />
+<glyph unicode="&#xe020;" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" />
+<glyph unicode="&#xe021;" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" />
+<glyph unicode="&#xe022;" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" />
+<glyph unicode="&#xe023;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe024;" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" />
+<glyph unicode="&#xe025;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe026;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " />
+<glyph unicode="&#xe027;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" />
+<glyph unicode="&#xe028;" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" />
+<glyph unicode="&#xe029;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
+<glyph unicode="&#xe030;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" />
+<glyph unicode="&#xe031;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" />
+<glyph unicode="&#xe032;" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe033;" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" />
+<glyph unicode="&#xe034;" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" />
+<glyph unicode="&#xe035;" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" />
+<glyph unicode="&#xe036;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" />
+<glyph unicode="&#xe037;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" />
+<glyph unicode="&#xe038;" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" />
+<glyph unicode="&#xe039;" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" />
+<glyph unicode="&#xe040;" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" />
+<glyph unicode="&#xe041;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
+<glyph unicode="&#xe042;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
+<glyph unicode="&#xe043;" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" />
+<glyph unicode="&#xe044;" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe045;" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" />
+<glyph unicode="&#xe046;" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" />
+<glyph unicode="&#xe047;" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" />
+<glyph unicode="&#xe048;" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" />
+<glyph unicode="&#xe049;" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" />
+<glyph unicode="&#xe050;" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" />
+<glyph unicode="&#xe051;" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" />
+<glyph unicode="&#xe052;" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe053;" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe054;" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe055;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe056;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe057;" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe058;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe059;" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" />
+<glyph unicode="&#xe060;" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" />
+<glyph unicode="&#xe062;" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" />
+<glyph unicode="&#xe063;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" />
+<glyph unicode="&#xe064;" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" />
+<glyph unicode="&#xe065;" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" />
+<glyph unicode="&#xe066;" d="M350 1100h361q-164 -146 -216 -200h-195q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" />
+<glyph unicode="&#xe067;" d="M350 1100h350q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" />
+<glyph unicode="&#xe068;" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" />
+<glyph unicode="&#xe069;" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe070;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe071;" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" />
+<glyph unicode="&#xe072;" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" />
+<glyph unicode="&#xe073;" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe074;" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" />
+<glyph unicode="&#xe075;" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" />
+<glyph unicode="&#xe076;" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe077;" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe078;" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe079;" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" />
+<glyph unicode="&#xe080;" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" />
+<glyph unicode="&#xe081;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" />
+<glyph unicode="&#xe082;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" />
+<glyph unicode="&#xe083;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" />
+<glyph unicode="&#xe084;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" />
+<glyph unicode="&#xe085;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe086;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe087;" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" />
+<glyph unicode="&#xe088;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" />
+<glyph unicode="&#xe089;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" />
+<glyph unicode="&#xe090;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" />
+<glyph unicode="&#xe091;" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" />
+<glyph unicode="&#xe092;" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" />
+<glyph unicode="&#xe093;" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" />
+<glyph unicode="&#xe094;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe095;" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" />
+<glyph unicode="&#xe096;" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" />
+<glyph unicode="&#xe097;" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" />
+<glyph unicode="&#xe101;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe102;" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" />
+<glyph unicode="&#xe103;" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" />
+<glyph unicode="&#xe104;" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" />
+<glyph unicode="&#xe105;" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
+<glyph unicode="&#xe106;" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
+<glyph unicode="&#xe107;" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" />
+<glyph unicode="&#xe108;" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" />
+<glyph unicode="&#xe109;" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" />
+<glyph unicode="&#xe110;" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" />
+<glyph unicode="&#xe111;" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" />
+<glyph unicode="&#xe112;" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" />
+<glyph unicode="&#xe113;" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" />
+<glyph unicode="&#xe114;" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" />
+<glyph unicode="&#xe115;" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe116;" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" />
+<glyph unicode="&#xe117;" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" />
+<glyph unicode="&#xe118;" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" />
+<glyph unicode="&#xe119;" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe120;" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" />
+<glyph unicode="&#xe121;" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" />
+<glyph unicode="&#xe122;" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" />
+<glyph unicode="&#xe123;" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" />
+<glyph unicode="&#xe124;" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" />
+<glyph unicode="&#xe125;" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe126;" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" />
+<glyph unicode="&#xe127;" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe128;" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe129;" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe130;" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" />
+<glyph unicode="&#xe131;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" />
+<glyph unicode="&#xe132;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" />
+<glyph unicode="&#xe133;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" />
+<glyph unicode="&#xe134;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe135;" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" />
+<glyph unicode="&#xe136;" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" />
+<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " />
+<glyph unicode="&#xe138;" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" />
+<glyph unicode="&#xe139;" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" />
+<glyph unicode="&#xe140;" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" />
+<glyph unicode="&#xe141;" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" />
+<glyph unicode="&#xe142;" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" />
+<glyph unicode="&#xe143;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" />
+<glyph unicode="&#xe144;" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" />
+<glyph unicode="&#xe145;" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" />
+<glyph unicode="&#xe146;" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" />
+<glyph unicode="&#xe148;" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" />
+<glyph unicode="&#xe149;" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" />
+<glyph unicode="&#xe150;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe151;" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " />
+<glyph unicode="&#xe152;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " />
+<glyph unicode="&#xe153;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" />
+<glyph unicode="&#xe154;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" />
+<glyph unicode="&#xe155;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" />
+<glyph unicode="&#xe156;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" />
+<glyph unicode="&#xe157;" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5z" />
+<glyph unicode="&#xe158;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
+<glyph unicode="&#xe159;" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" />
+<glyph unicode="&#xe160;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" />
+<glyph unicode="&#xe161;" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
+<glyph unicode="&#xe162;" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" />
+<glyph unicode="&#xe163;" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
+<glyph unicode="&#xe164;" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" />
+<glyph unicode="&#xe165;" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" />
+<glyph unicode="&#xe166;" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe167;" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe168;" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" />
+<glyph unicode="&#xe169;" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe170;" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
+<glyph unicode="&#xe171;" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" />
+<glyph unicode="&#xe172;" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" />
+<glyph unicode="&#xe173;" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" />
+<glyph unicode="&#xe174;" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" />
+<glyph unicode="&#xe175;" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe176;" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" />
+<glyph unicode="&#xe177;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" />
+<glyph unicode="&#xe178;" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" />
+<glyph unicode="&#xe179;" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" />
+<glyph unicode="&#xe180;" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" />
+<glyph unicode="&#xe181;" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" />
+<glyph unicode="&#xe182;" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" />
+<glyph unicode="&#xe183;" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" />
+<glyph unicode="&#xe184;" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe185;" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " />
+<glyph unicode="&#xe186;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
+<glyph unicode="&#xe187;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
+<glyph unicode="&#xe188;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" />
+<glyph unicode="&#xe189;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" />
+<glyph unicode="&#xe190;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" />
+<glyph unicode="&#xe191;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" />
+<glyph unicode="&#xe192;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" />
+<glyph unicode="&#xe193;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" />
+<glyph unicode="&#xe194;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" />
+<glyph unicode="&#xe195;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" />
+<glyph unicode="&#xe197;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe198;" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" />
+<glyph unicode="&#xe199;" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" />
+<glyph unicode="&#xe200;" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" />
+<glyph unicode="&#xe201;" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" />
+<glyph unicode="&#xe202;" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" />
+<glyph unicode="&#xe203;" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" />
+<glyph unicode="&#xe204;" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" />
+<glyph unicode="&#xe205;" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" />
+<glyph unicode="&#xe206;" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" />
+<glyph unicode="&#xe209;" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" />
+<glyph unicode="&#xe210;" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe211;" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe212;" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe213;" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe214;" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe215;" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe216;" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" />
+<glyph unicode="&#xe218;" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" />
+<glyph unicode="&#xe219;" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" />
+<glyph unicode="&#xe221;" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe223;" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" />
+<glyph unicode="&#xe224;" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " />
+<glyph unicode="&#xe225;" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" />
+<glyph unicode="&#xe226;" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" />
+<glyph unicode="&#xe227;" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" />
+<glyph unicode="&#xe230;" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" />
+<glyph unicode="&#xe231;" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
+<glyph unicode="&#xe232;" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
+<glyph unicode="&#xe233;" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" />
+<glyph unicode="&#xe234;" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
+<glyph unicode="&#xe235;" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
+<glyph unicode="&#xe236;" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
+<glyph unicode="&#xe237;" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" />
+<glyph unicode="&#xe238;" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe239;" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" />
+<glyph unicode="&#xe240;" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" />
+<glyph unicode="&#xe241;" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" />
+<glyph unicode="&#xe242;" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" />
+<glyph unicode="&#xe243;" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" />
+<glyph unicode="&#xe244;" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" />
+<glyph unicode="&#xe245;" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" />
+<glyph unicode="&#xe246;" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" />
+<glyph unicode="&#xe247;" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe248;" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" />
+<glyph unicode="&#xe249;" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
+<glyph unicode="&#xe250;" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" />
+<glyph unicode="&#xe251;" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" />
+<glyph unicode="&#xe252;" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" />
+<glyph unicode="&#xe253;" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" />
+<glyph unicode="&#xe254;" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" />
+<glyph unicode="&#xe255;" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" />
+<glyph unicode="&#xe256;" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" />
+<glyph unicode="&#xe257;" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" />
+<glyph unicode="&#xe258;" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" />
+<glyph unicode="&#xe259;" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" />
+<glyph unicode="&#xe260;" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" />
+<glyph unicode="&#xf8ff;" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" />
+<glyph unicode="&#x1f511;" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" />
+<glyph unicode="&#x1f6aa;" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" />
+</font>
+</defs></svg> \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 000000000..1413fc609
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 000000000..9e612858f
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2 b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2
new file mode 100644
index 000000000..64539b54c
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js
new file mode 100644
index 000000000..4139b6fc3
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js
@@ -0,0 +1,2306 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+if (typeof jQuery === 'undefined') {
+ throw new Error('Bootstrap\'s JavaScript requires jQuery')
+}
+
++function ($) {
+ 'use strict';
+ var version = $.fn.jquery.split(' ')[0].split('.')
+ if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
+ throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
+ }
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.3.2
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+ // ============================================================
+
+ function transitionEnd() {
+ var el = document.createElement('bootstrap')
+
+ var transEndEventNames = {
+ WebkitTransition : 'webkitTransitionEnd',
+ MozTransition : 'transitionend',
+ OTransition : 'oTransitionEnd otransitionend',
+ transition : 'transitionend'
+ }
+
+ for (var name in transEndEventNames) {
+ if (el.style[name] !== undefined) {
+ return { end: transEndEventNames[name] }
+ }
+ }
+
+ return false // explicit for ie8 ( ._.)
+ }
+
+ // http://blog.alexmaccaw.com/css-transitions
+ $.fn.emulateTransitionEnd = function (duration) {
+ var called = false
+ var $el = this
+ $(this).one('bsTransitionEnd', function () { called = true })
+ var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+ setTimeout(callback, duration)
+ return this
+ }
+
+ $(function () {
+ $.support.transition = transitionEnd()
+
+ if (!$.support.transition) return
+
+ $.event.special.bsTransitionEnd = {
+ bindType: $.support.transition.end,
+ delegateType: $.support.transition.end,
+ handle: function (e) {
+ if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+ }
+ }
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.3.2
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // ALERT CLASS DEFINITION
+ // ======================
+
+ var dismiss = '[data-dismiss="alert"]'
+ var Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.VERSION = '3.3.2'
+
+ Alert.TRANSITION_DURATION = 150
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = $(selector)
+
+ if (e) e.preventDefault()
+
+ if (!$parent.length) {
+ $parent = $this.closest('.alert')
+ }
+
+ $parent.trigger(e = $.Event('close.bs.alert'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ // detach from parent, fire event then clean up data
+ $parent.detach().trigger('closed.bs.alert').remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent
+ .one('bsTransitionEnd', removeElement)
+ .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
+ removeElement()
+ }
+
+
+ // ALERT PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.alert')
+
+ if (!data) $this.data('bs.alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.alert
+
+ $.fn.alert = Plugin
+ $.fn.alert.Constructor = Alert
+
+
+ // ALERT NO CONFLICT
+ // =================
+
+ $.fn.alert.noConflict = function () {
+ $.fn.alert = old
+ return this
+ }
+
+
+ // ALERT DATA-API
+ // ==============
+
+ $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.3.2
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // BUTTON PUBLIC CLASS DEFINITION
+ // ==============================
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Button.DEFAULTS, options)
+ this.isLoading = false
+ }
+
+ Button.VERSION = '3.3.2'
+
+ Button.DEFAULTS = {
+ loadingText: 'loading...'
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ var $el = this.$element
+ var val = $el.is('input') ? 'val' : 'html'
+ var data = $el.data()
+
+ state = state + 'Text'
+
+ if (data.resetText == null) $el.data('resetText', $el[val]())
+
+ // push to event loop to allow forms to submit
+ setTimeout($.proxy(function () {
+ $el[val](data[state] == null ? this.options[state] : data[state])
+
+ if (state == 'loadingText') {
+ this.isLoading = true
+ $el.addClass(d).attr(d, d)
+ } else if (this.isLoading) {
+ this.isLoading = false
+ $el.removeClass(d).removeAttr(d)
+ }
+ }, this), 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var changed = true
+ var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+ if ($parent.length) {
+ var $input = this.$element.find('input')
+ if ($input.prop('type') == 'radio') {
+ if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
+ else $parent.find('.active').removeClass('active')
+ }
+ if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
+ } else {
+ this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
+ }
+
+ if (changed) this.$element.toggleClass('active')
+ }
+
+
+ // BUTTON PLUGIN DEFINITION
+ // ========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.button')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ var old = $.fn.button
+
+ $.fn.button = Plugin
+ $.fn.button.Constructor = Button
+
+
+ // BUTTON NO CONFLICT
+ // ==================
+
+ $.fn.button.noConflict = function () {
+ $.fn.button = old
+ return this
+ }
+
+
+ // BUTTON DATA-API
+ // ===============
+
+ $(document)
+ .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ Plugin.call($btn, 'toggle')
+ e.preventDefault()
+ })
+ .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.3.2
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CAROUSEL CLASS DEFINITION
+ // =========================
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.$indicators = this.$element.find('.carousel-indicators')
+ this.options = options
+ this.paused =
+ this.sliding =
+ this.interval =
+ this.$active =
+ this.$items = null
+
+ this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
+
+ this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
+ .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+ .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+ }
+
+ Carousel.VERSION = '3.3.2'
+
+ Carousel.TRANSITION_DURATION = 600
+
+ Carousel.DEFAULTS = {
+ interval: 5000,
+ pause: 'hover',
+ wrap: true,
+ keyboard: true
+ }
+
+ Carousel.prototype.keydown = function (e) {
+ if (/input|textarea/i.test(e.target.tagName)) return
+ switch (e.which) {
+ case 37: this.prev(); break
+ case 39: this.next(); break
+ default: return
+ }
+
+ e.preventDefault()
+ }
+
+ Carousel.prototype.cycle = function (e) {
+ e || (this.paused = false)
+
+ this.interval && clearInterval(this.interval)
+
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+ return this
+ }
+
+ Carousel.prototype.getItemIndex = function (item) {
+ this.$items = item.parent().children('.item')
+ return this.$items.index(item || this.$active)
+ }
+
+ Carousel.prototype.getItemForDirection = function (direction, active) {
+ var activeIndex = this.getItemIndex(active)
+ var willWrap = (direction == 'prev' && activeIndex === 0)
+ || (direction == 'next' && activeIndex == (this.$items.length - 1))
+ if (willWrap && !this.options.wrap) return active
+ var delta = direction == 'prev' ? -1 : 1
+ var itemIndex = (activeIndex + delta) % this.$items.length
+ return this.$items.eq(itemIndex)
+ }
+
+ Carousel.prototype.to = function (pos) {
+ var that = this
+ var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+ if (pos > (this.$items.length - 1) || pos < 0) return
+
+ if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+ if (activeIndex == pos) return this.pause().cycle()
+
+ return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
+ }
+
+ Carousel.prototype.pause = function (e) {
+ e || (this.paused = true)
+
+ if (this.$element.find('.next, .prev').length && $.support.transition) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle(true)
+ }
+
+ this.interval = clearInterval(this.interval)
+
+ return this
+ }
+
+ Carousel.prototype.next = function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ Carousel.prototype.prev = function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ Carousel.prototype.slide = function (type, next) {
+ var $active = this.$element.find('.item.active')
+ var $next = next || this.getItemForDirection(type, $active)
+ var isCycling = this.interval
+ var direction = type == 'next' ? 'left' : 'right'
+ var that = this
+
+ if ($next.hasClass('active')) return (this.sliding = false)
+
+ var relatedTarget = $next[0]
+ var slideEvent = $.Event('slide.bs.carousel', {
+ relatedTarget: relatedTarget,
+ direction: direction
+ })
+ this.$element.trigger(slideEvent)
+ if (slideEvent.isDefaultPrevented()) return
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ if (this.$indicators.length) {
+ this.$indicators.find('.active').removeClass('active')
+ var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+ $nextIndicator && $nextIndicator.addClass('active')
+ }
+
+ var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ $active
+ .one('bsTransitionEnd', function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () {
+ that.$element.trigger(slidEvent)
+ }, 0)
+ })
+ .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
+ } else {
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger(slidEvent)
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+
+ // CAROUSEL PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.carousel')
+ var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ var action = typeof option == 'string' ? option : options.slide
+
+ if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.pause().cycle()
+ })
+ }
+
+ var old = $.fn.carousel
+
+ $.fn.carousel = Plugin
+ $.fn.carousel.Constructor = Carousel
+
+
+ // CAROUSEL NO CONFLICT
+ // ====================
+
+ $.fn.carousel.noConflict = function () {
+ $.fn.carousel = old
+ return this
+ }
+
+
+ // CAROUSEL DATA-API
+ // =================
+
+ var clickHandler = function (e) {
+ var href
+ var $this = $(this)
+ var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+ if (!$target.hasClass('carousel')) return
+ var options = $.extend({}, $target.data(), $this.data())
+ var slideIndex = $this.attr('data-slide-to')
+ if (slideIndex) options.interval = false
+
+ Plugin.call($target, options)
+
+ if (slideIndex) {
+ $target.data('bs.carousel').to(slideIndex)
+ }
+
+ e.preventDefault()
+ }
+
+ $(document)
+ .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
+ .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
+
+ $(window).on('load', function () {
+ $('[data-ride="carousel"]').each(function () {
+ var $carousel = $(this)
+ Plugin.call($carousel, $carousel.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.3.2
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // COLLAPSE PUBLIC CLASS DEFINITION
+ // ================================
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Collapse.DEFAULTS, options)
+ this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]')
+ this.transitioning = null
+
+ if (this.options.parent) {
+ this.$parent = this.getParent()
+ } else {
+ this.addAriaAndCollapsedClass(this.$element, this.$trigger)
+ }
+
+ if (this.options.toggle) this.toggle()
+ }
+
+ Collapse.VERSION = '3.3.2'
+
+ Collapse.TRANSITION_DURATION = 350
+
+ Collapse.DEFAULTS = {
+ toggle: true,
+ trigger: '[data-toggle="collapse"]'
+ }
+
+ Collapse.prototype.dimension = function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ Collapse.prototype.show = function () {
+ if (this.transitioning || this.$element.hasClass('in')) return
+
+ var activesData
+ var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
+
+ if (actives && actives.length) {
+ activesData = actives.data('bs.collapse')
+ if (activesData && activesData.transitioning) return
+ }
+
+ var startEvent = $.Event('show.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ if (actives && actives.length) {
+ Plugin.call(actives, 'hide')
+ activesData || actives.data('bs.collapse', null)
+ }
+
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ .addClass('collapsing')[dimension](0)
+ .attr('aria-expanded', true)
+
+ this.$trigger
+ .removeClass('collapsed')
+ .attr('aria-expanded', true)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse in')[dimension]('')
+ this.transitioning = 0
+ this.$element
+ .trigger('shown.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+ this.$element
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
+ }
+
+ Collapse.prototype.hide = function () {
+ if (this.transitioning || !this.$element.hasClass('in')) return
+
+ var startEvent = $.Event('hide.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ var dimension = this.dimension()
+
+ this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+ this.$element
+ .addClass('collapsing')
+ .removeClass('collapse in')
+ .attr('aria-expanded', false)
+
+ this.$trigger
+ .addClass('collapsed')
+ .attr('aria-expanded', false)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.transitioning = 0
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse')
+ .trigger('hidden.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ this.$element
+ [dimension](0)
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
+ }
+
+ Collapse.prototype.toggle = function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ Collapse.prototype.getParent = function () {
+ return $(this.options.parent)
+ .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
+ .each($.proxy(function (i, element) {
+ var $element = $(element)
+ this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
+ }, this))
+ .end()
+ }
+
+ Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
+ var isOpen = $element.hasClass('in')
+
+ $element.attr('aria-expanded', isOpen)
+ $trigger
+ .toggleClass('collapsed', !isOpen)
+ .attr('aria-expanded', isOpen)
+ }
+
+ function getTargetFromTrigger($trigger) {
+ var href
+ var target = $trigger.attr('data-target')
+ || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+
+ return $(target)
+ }
+
+
+ // COLLAPSE PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.collapse')
+ var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data && options.toggle && option == 'show') options.toggle = false
+ if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.collapse
+
+ $.fn.collapse = Plugin
+ $.fn.collapse.Constructor = Collapse
+
+
+ // COLLAPSE NO CONFLICT
+ // ====================
+
+ $.fn.collapse.noConflict = function () {
+ $.fn.collapse = old
+ return this
+ }
+
+
+ // COLLAPSE DATA-API
+ // =================
+
+ $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+ var $this = $(this)
+
+ if (!$this.attr('data-target')) e.preventDefault()
+
+ var $target = getTargetFromTrigger($this)
+ var data = $target.data('bs.collapse')
+ var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })
+
+ Plugin.call($target, option)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.2
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // DROPDOWN CLASS DEFINITION
+ // =========================
+
+ var backdrop = '.dropdown-backdrop'
+ var toggle = '[data-toggle="dropdown"]'
+ var Dropdown = function (element) {
+ $(element).on('click.bs.dropdown', this.toggle)
+ }
+
+ Dropdown.VERSION = '3.3.2'
+
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this)
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+ }
+
+ var relatedTarget = { relatedTarget: this }
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this
+ .trigger('focus')
+ .attr('aria-expanded', 'true')
+
+ $parent
+ .toggleClass('open')
+ .trigger('shown.bs.dropdown', relatedTarget)
+ }
+
+ return false
+ }
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+ var $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
+ if (e.which == 27) $parent.find(toggle).trigger('focus')
+ return $this.trigger('click')
+ }
+
+ var desc = ' li:not(.divider):visible a'
+ var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
+
+ if (!$items.length) return
+
+ var index = $items.index(e.target)
+
+ if (e.which == 38 && index > 0) index-- // up
+ if (e.which == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items.eq(index).trigger('focus')
+ }
+
+ function clearMenus(e) {
+ if (e && e.which === 3) return
+ $(backdrop).remove()
+ $(toggle).each(function () {
+ var $this = $(this)
+ var $parent = getParent($this)
+ var relatedTarget = { relatedTarget: this }
+
+ if (!$parent.hasClass('open')) return
+
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this.attr('aria-expanded', 'false')
+ $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
+ })
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = selector && $(selector)
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.dropdown')
+
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = Plugin
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ // APPLY TO STANDARD DROPDOWN ELEMENTS
+ // ===================================
+
+ $(document)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.3.2
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // MODAL CLASS DEFINITION
+ // ======================
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$body = $(document.body)
+ this.$element = $(element)
+ this.$backdrop =
+ this.isShown = null
+ this.scrollbarWidth = 0
+
+ if (this.options.remote) {
+ this.$element
+ .find('.modal-content')
+ .load(this.options.remote, $.proxy(function () {
+ this.$element.trigger('loaded.bs.modal')
+ }, this))
+ }
+ }
+
+ Modal.VERSION = '3.3.2'
+
+ Modal.TRANSITION_DURATION = 300
+ Modal.BACKDROP_TRANSITION_DURATION = 150
+
+ Modal.DEFAULTS = {
+ backdrop: true,
+ keyboard: true,
+ show: true
+ }
+
+ Modal.prototype.toggle = function (_relatedTarget) {
+ return this.isShown ? this.hide() : this.show(_relatedTarget)
+ }
+
+ Modal.prototype.show = function (_relatedTarget) {
+ var that = this
+ var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = true
+
+ this.checkScrollbar()
+ this.setScrollbar()
+ this.$body.addClass('modal-open')
+
+ this.escape()
+ this.resize()
+
+ this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(that.$body) // don't move modals dom position
+ }
+
+ that.$element
+ .show()
+ .scrollTop(0)
+
+ if (that.options.backdrop) that.adjustBackdrop()
+ that.adjustDialog()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+
+ that.enforceFocus()
+
+ var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+ transition ?
+ that.$element.find('.modal-dialog') // wait for modal to slide in
+ .one('bsTransitionEnd', function () {
+ that.$element.trigger('focus').trigger(e)
+ })
+ .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+ that.$element.trigger('focus').trigger(e)
+ })
+ }
+
+ Modal.prototype.hide = function (e) {
+ if (e) e.preventDefault()
+
+ e = $.Event('hide.bs.modal')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ this.escape()
+ this.resize()
+
+ $(document).off('focusin.bs.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+ .off('click.dismiss.bs.modal')
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$element
+ .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+ .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+ this.hideModal()
+ }
+
+ Modal.prototype.enforceFocus = function () {
+ $(document)
+ .off('focusin.bs.modal') // guard against infinite focus loop
+ .on('focusin.bs.modal', $.proxy(function (e) {
+ if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+ this.$element.trigger('focus')
+ }
+ }, this))
+ }
+
+ Modal.prototype.escape = function () {
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
+ e.which == 27 && this.hide()
+ }, this))
+ } else if (!this.isShown) {
+ this.$element.off('keydown.dismiss.bs.modal')
+ }
+ }
+
+ Modal.prototype.resize = function () {
+ if (this.isShown) {
+ $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
+ } else {
+ $(window).off('resize.bs.modal')
+ }
+ }
+
+ Modal.prototype.hideModal = function () {
+ var that = this
+ this.$element.hide()
+ this.backdrop(function () {
+ that.$body.removeClass('modal-open')
+ that.resetAdjustments()
+ that.resetScrollbar()
+ that.$element.trigger('hidden.bs.modal')
+ })
+ }
+
+ Modal.prototype.removeBackdrop = function () {
+ this.$backdrop && this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ Modal.prototype.backdrop = function (callback) {
+ var that = this
+ var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .prependTo(this.$element)
+ .on('click.dismiss.bs.modal', $.proxy(function (e) {
+ if (e.target !== e.currentTarget) return
+ this.options.backdrop == 'static'
+ ? this.$element[0].focus.call(this.$element[0])
+ : this.hide.call(this)
+ }, this))
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ if (!callback) return
+
+ doAnimate ?
+ this.$backdrop
+ .one('bsTransitionEnd', callback)
+ .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ var callbackRemove = function () {
+ that.removeBackdrop()
+ callback && callback()
+ }
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$backdrop
+ .one('bsTransitionEnd', callbackRemove)
+ .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+ callbackRemove()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+
+ // these following methods are used to handle overflowing modals
+
+ Modal.prototype.handleUpdate = function () {
+ if (this.options.backdrop) this.adjustBackdrop()
+ this.adjustDialog()
+ }
+
+ Modal.prototype.adjustBackdrop = function () {
+ this.$backdrop
+ .css('height', 0)
+ .css('height', this.$element[0].scrollHeight)
+ }
+
+ Modal.prototype.adjustDialog = function () {
+ var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
+
+ this.$element.css({
+ paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
+ paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
+ })
+ }
+
+ Modal.prototype.resetAdjustments = function () {
+ this.$element.css({
+ paddingLeft: '',
+ paddingRight: ''
+ })
+ }
+
+ Modal.prototype.checkScrollbar = function () {
+ this.bodyIsOverflowing = document.body.scrollHeight > document.documentElement.clientHeight
+ this.scrollbarWidth = this.measureScrollbar()
+ }
+
+ Modal.prototype.setScrollbar = function () {
+ var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+ if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+ }
+
+ Modal.prototype.resetScrollbar = function () {
+ this.$body.css('padding-right', '')
+ }
+
+ Modal.prototype.measureScrollbar = function () { // thx walsh
+ var scrollDiv = document.createElement('div')
+ scrollDiv.className = 'modal-scrollbar-measure'
+ this.$body.append(scrollDiv)
+ var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+ this.$body[0].removeChild(scrollDiv)
+ return scrollbarWidth
+ }
+
+
+ // MODAL PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option, _relatedTarget) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.modal')
+ var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option](_relatedTarget)
+ else if (options.show) data.show(_relatedTarget)
+ })
+ }
+
+ var old = $.fn.modal
+
+ $.fn.modal = Plugin
+ $.fn.modal.Constructor = Modal
+
+
+ // MODAL NO CONFLICT
+ // =================
+
+ $.fn.modal.noConflict = function () {
+ $.fn.modal = old
+ return this
+ }
+
+
+ // MODAL DATA-API
+ // ==============
+
+ $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+ var $this = $(this)
+ var href = $this.attr('href')
+ var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+ var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ if ($this.is('a')) e.preventDefault()
+
+ $target.one('show.bs.modal', function (showEvent) {
+ if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+ $target.one('hidden.bs.modal', function () {
+ $this.is(':visible') && $this.trigger('focus')
+ })
+ })
+ Plugin.call($target, option, this)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.3.2
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TOOLTIP PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Tooltip = function (element, options) {
+ this.type =
+ this.options =
+ this.enabled =
+ this.timeout =
+ this.hoverState =
+ this.$element = null
+
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.VERSION = '3.3.2'
+
+ Tooltip.TRANSITION_DURATION = 150
+
+ Tooltip.DEFAULTS = {
+ animation: true,
+ placement: 'top',
+ selector: false,
+ template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+ trigger: 'hover focus',
+ title: '',
+ delay: 0,
+ html: false,
+ container: false,
+ viewport: {
+ selector: 'body',
+ padding: 0
+ }
+ }
+
+ Tooltip.prototype.init = function (type, element, options) {
+ this.enabled = true
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
+
+ var triggers = this.options.trigger.split(' ')
+
+ for (var i = triggers.length; i--;) {
+ var trigger = triggers[i]
+
+ if (trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (trigger != 'manual') {
+ var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
+ var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ Tooltip.prototype.getDefaults = function () {
+ return Tooltip.DEFAULTS
+ }
+
+ Tooltip.prototype.getOptions = function (options) {
+ options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay,
+ hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ Tooltip.prototype.getDelegateOptions = function () {
+ var options = {}
+ var defaults = this.getDefaults()
+
+ this._options && $.each(this._options, function (key, value) {
+ if (defaults[key] != value) options[key] = value
+ })
+
+ return options
+ }
+
+ Tooltip.prototype.enter = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (self && self.$tip && self.$tip.is(':visible')) {
+ self.hoverState = 'in'
+ return
+ }
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'in'
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ Tooltip.prototype.leave = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'out'
+
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ Tooltip.prototype.show = function () {
+ var e = $.Event('show.bs.' + this.type)
+
+ if (this.hasContent() && this.enabled) {
+ this.$element.trigger(e)
+
+ var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
+ if (e.isDefaultPrevented() || !inDom) return
+ var that = this
+
+ var $tip = this.tip()
+
+ var tipId = this.getUID(this.type)
+
+ this.setContent()
+ $tip.attr('id', tipId)
+ this.$element.attr('aria-describedby', tipId)
+
+ if (this.options.animation) $tip.addClass('fade')
+
+ var placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ var autoToken = /\s?auto?\s?/i
+ var autoPlace = autoToken.test(placement)
+ if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+ $tip
+ .detach()
+ .css({ top: 0, left: 0, display: 'block' })
+ .addClass(placement)
+ .data('bs.' + this.type, this)
+
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+ var pos = this.getPosition()
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (autoPlace) {
+ var orgPlacement = placement
+ var $container = this.options.container ? $(this.options.container) : this.$element.parent()
+ var containerDim = this.getPosition($container)
+
+ placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
+ placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
+ placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
+ placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
+ placement
+
+ $tip
+ .removeClass(orgPlacement)
+ .addClass(placement)
+ }
+
+ var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+ this.applyPlacement(calculatedOffset, placement)
+
+ var complete = function () {
+ var prevHoverState = that.hoverState
+ that.$element.trigger('shown.bs.' + that.type)
+ that.hoverState = null
+
+ if (prevHoverState == 'out') that.leave(that)
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+ complete()
+ }
+ }
+
+ Tooltip.prototype.applyPlacement = function (offset, placement) {
+ var $tip = this.tip()
+ var width = $tip[0].offsetWidth
+ var height = $tip[0].offsetHeight
+
+ // manually read margins because getBoundingClientRect includes difference
+ var marginTop = parseInt($tip.css('margin-top'), 10)
+ var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+ // we must check for NaN for ie 8/9
+ if (isNaN(marginTop)) marginTop = 0
+ if (isNaN(marginLeft)) marginLeft = 0
+
+ offset.top = offset.top + marginTop
+ offset.left = offset.left + marginLeft
+
+ // $.fn.offset doesn't round pixel values
+ // so we use setOffset directly with our own function B-0
+ $.offset.setOffset($tip[0], $.extend({
+ using: function (props) {
+ $tip.css({
+ top: Math.round(props.top),
+ left: Math.round(props.left)
+ })
+ }
+ }, offset), 0)
+
+ $tip.addClass('in')
+
+ // check to see if placing tip in new offset caused the tip to resize itself
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (placement == 'top' && actualHeight != height) {
+ offset.top = offset.top + height - actualHeight
+ }
+
+ var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+ if (delta.left) offset.left += delta.left
+ else offset.top += delta.top
+
+ var isVertical = /top|bottom/.test(placement)
+ var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+ var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
+
+ $tip.offset(offset)
+ this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
+ }
+
+ Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
+ this.arrow()
+ .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
+ .css(isHorizontal ? 'top' : 'left', '')
+ }
+
+ Tooltip.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ Tooltip.prototype.hide = function (callback) {
+ var that = this
+ var $tip = this.tip()
+ var e = $.Event('hide.bs.' + this.type)
+
+ function complete() {
+ if (that.hoverState != 'in') $tip.detach()
+ that.$element
+ .removeAttr('aria-describedby')
+ .trigger('hidden.bs.' + that.type)
+ callback && callback()
+ }
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $tip.removeClass('in')
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+ complete()
+
+ this.hoverState = null
+
+ return this
+ }
+
+ Tooltip.prototype.fixTitle = function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+ }
+ }
+
+ Tooltip.prototype.hasContent = function () {
+ return this.getTitle()
+ }
+
+ Tooltip.prototype.getPosition = function ($element) {
+ $element = $element || this.$element
+
+ var el = $element[0]
+ var isBody = el.tagName == 'BODY'
+
+ var elRect = el.getBoundingClientRect()
+ if (elRect.width == null) {
+ // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
+ elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
+ }
+ var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
+ var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
+ var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
+
+ return $.extend({}, elRect, scroll, outerDims, elOffset)
+ }
+
+ Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+ return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+ /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+
+ }
+
+ Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+ var delta = { top: 0, left: 0 }
+ if (!this.$viewport) return delta
+
+ var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+ var viewportDimensions = this.getPosition(this.$viewport)
+
+ if (/right|left/.test(placement)) {
+ var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
+ var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+ if (topEdgeOffset < viewportDimensions.top) { // top overflow
+ delta.top = viewportDimensions.top - topEdgeOffset
+ } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+ }
+ } else {
+ var leftEdgeOffset = pos.left - viewportPadding
+ var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+ if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+ delta.left = viewportDimensions.left - leftEdgeOffset
+ } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+ }
+ }
+
+ return delta
+ }
+
+ Tooltip.prototype.getTitle = function () {
+ var title
+ var $e = this.$element
+ var o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ Tooltip.prototype.getUID = function (prefix) {
+ do prefix += ~~(Math.random() * 1000000)
+ while (document.getElementById(prefix))
+ return prefix
+ }
+
+ Tooltip.prototype.tip = function () {
+ return (this.$tip = this.$tip || $(this.options.template))
+ }
+
+ Tooltip.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+ }
+
+ Tooltip.prototype.enable = function () {
+ this.enabled = true
+ }
+
+ Tooltip.prototype.disable = function () {
+ this.enabled = false
+ }
+
+ Tooltip.prototype.toggleEnabled = function () {
+ this.enabled = !this.enabled
+ }
+
+ Tooltip.prototype.toggle = function (e) {
+ var self = this
+ if (e) {
+ self = $(e.currentTarget).data('bs.' + this.type)
+ if (!self) {
+ self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+ $(e.currentTarget).data('bs.' + this.type, self)
+ }
+ }
+
+ self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+ }
+
+ Tooltip.prototype.destroy = function () {
+ var that = this
+ clearTimeout(this.timeout)
+ this.hide(function () {
+ that.$element.off('.' + that.type).removeData('bs.' + that.type)
+ })
+ }
+
+
+ // TOOLTIP PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tooltip')
+ var options = typeof option == 'object' && option
+
+ if (!data && option == 'destroy') return
+ if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tooltip
+
+ $.fn.tooltip = Plugin
+ $.fn.tooltip.Constructor = Tooltip
+
+
+ // TOOLTIP NO CONFLICT
+ // ===================
+
+ $.fn.tooltip.noConflict = function () {
+ $.fn.tooltip = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.3.2
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // POPOVER PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+ Popover.VERSION = '3.3.2'
+
+ Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+ placement: 'right',
+ trigger: 'click',
+ content: '',
+ template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+ })
+
+
+ // NOTE: POPOVER EXTENDS tooltip.js
+ // ================================
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+ Popover.prototype.constructor = Popover
+
+ Popover.prototype.getDefaults = function () {
+ return Popover.DEFAULTS
+ }
+
+ Popover.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+ var content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
+ this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+ ](content)
+
+ $tip.removeClass('fade top bottom left right in')
+
+ // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+ // this manually by checking the contents.
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+ }
+
+ Popover.prototype.hasContent = function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ Popover.prototype.getContent = function () {
+ var $e = this.$element
+ var o = this.options
+
+ return $e.attr('data-content')
+ || (typeof o.content == 'function' ?
+ o.content.call($e[0]) :
+ o.content)
+ }
+
+ Popover.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+ }
+
+ Popover.prototype.tip = function () {
+ if (!this.$tip) this.$tip = $(this.options.template)
+ return this.$tip
+ }
+
+
+ // POPOVER PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.popover')
+ var options = typeof option == 'object' && option
+
+ if (!data && option == 'destroy') return
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.popover
+
+ $.fn.popover = Plugin
+ $.fn.popover.Constructor = Popover
+
+
+ // POPOVER NO CONFLICT
+ // ===================
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.3.2
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // SCROLLSPY CLASS DEFINITION
+ // ==========================
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+
+ this.$body = $('body')
+ this.$scrollElement = $(element).is('body') ? $(window) : $(element)
+ this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
+ this.selector = (this.options.target || '') + ' .nav li > a'
+ this.offsets = []
+ this.targets = []
+ this.activeTarget = null
+ this.scrollHeight = 0
+
+ this.$scrollElement.on('scroll.bs.scrollspy', process)
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.VERSION = '3.3.2'
+
+ ScrollSpy.DEFAULTS = {
+ offset: 10
+ }
+
+ ScrollSpy.prototype.getScrollHeight = function () {
+ return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+ }
+
+ ScrollSpy.prototype.refresh = function () {
+ var offsetMethod = 'offset'
+ var offsetBase = 0
+
+ if (!$.isWindow(this.$scrollElement[0])) {
+ offsetMethod = 'position'
+ offsetBase = this.$scrollElement.scrollTop()
+ }
+
+ this.offsets = []
+ this.targets = []
+ this.scrollHeight = this.getScrollHeight()
+
+ var self = this
+
+ this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ var href = $el.data('target') || $el.attr('href')
+ var $href = /^#./.test(href) && $(href)
+
+ return ($href
+ && $href.length
+ && $href.is(':visible')
+ && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ ScrollSpy.prototype.process = function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ var scrollHeight = this.getScrollHeight()
+ var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
+ var offsets = this.offsets
+ var targets = this.targets
+ var activeTarget = this.activeTarget
+ var i
+
+ if (this.scrollHeight != scrollHeight) {
+ this.refresh()
+ }
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+ }
+
+ if (activeTarget && scrollTop < offsets[0]) {
+ this.activeTarget = null
+ return this.clear()
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate(targets[i])
+ }
+ }
+
+ ScrollSpy.prototype.activate = function (target) {
+ this.activeTarget = target
+
+ this.clear()
+
+ var selector = this.selector +
+ '[data-target="' + target + '"],' +
+ this.selector + '[href="' + target + '"]'
+
+ var active = $(selector)
+ .parents('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active
+ .closest('li.dropdown')
+ .addClass('active')
+ }
+
+ active.trigger('activate.bs.scrollspy')
+ }
+
+ ScrollSpy.prototype.clear = function () {
+ $(this.selector)
+ .parentsUntil(this.options.target, '.active')
+ .removeClass('active')
+ }
+
+
+ // SCROLLSPY PLUGIN DEFINITION
+ // ===========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.scrollspy')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.scrollspy
+
+ $.fn.scrollspy = Plugin
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+
+ // SCROLLSPY NO CONFLICT
+ // =====================
+
+ $.fn.scrollspy.noConflict = function () {
+ $.fn.scrollspy = old
+ return this
+ }
+
+
+ // SCROLLSPY DATA-API
+ // ==================
+
+ $(window).on('load.bs.scrollspy.data-api', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ Plugin.call($spy, $spy.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.3.2
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TAB CLASS DEFINITION
+ // ====================
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.VERSION = '3.3.2'
+
+ Tab.TRANSITION_DURATION = 150
+
+ Tab.prototype.show = function () {
+ var $this = this.element
+ var $ul = $this.closest('ul:not(.dropdown-menu)')
+ var selector = $this.data('target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ if ($this.parent('li').hasClass('active')) return
+
+ var $previous = $ul.find('.active:last a')
+ var hideEvent = $.Event('hide.bs.tab', {
+ relatedTarget: $this[0]
+ })
+ var showEvent = $.Event('show.bs.tab', {
+ relatedTarget: $previous[0]
+ })
+
+ $previous.trigger(hideEvent)
+ $this.trigger(showEvent)
+
+ if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
+
+ var $target = $(selector)
+
+ this.activate($this.closest('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $previous.trigger({
+ type: 'hidden.bs.tab',
+ relatedTarget: $this[0]
+ })
+ $this.trigger({
+ type: 'shown.bs.tab',
+ relatedTarget: $previous[0]
+ })
+ })
+ }
+
+ Tab.prototype.activate = function (element, container, callback) {
+ var $active = container.find('> .active')
+ var transition = callback
+ && $.support.transition
+ && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', false)
+
+ element
+ .addClass('active')
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if (element.parent('.dropdown-menu')) {
+ element
+ .closest('li.dropdown')
+ .addClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+ }
+
+ callback && callback()
+ }
+
+ $active.length && transition ?
+ $active
+ .one('bsTransitionEnd', next)
+ .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
+ next()
+
+ $active.removeClass('in')
+ }
+
+
+ // TAB PLUGIN DEFINITION
+ // =====================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tab')
+
+ if (!data) $this.data('bs.tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tab
+
+ $.fn.tab = Plugin
+ $.fn.tab.Constructor = Tab
+
+
+ // TAB NO CONFLICT
+ // ===============
+
+ $.fn.tab.noConflict = function () {
+ $.fn.tab = old
+ return this
+ }
+
+
+ // TAB DATA-API
+ // ============
+
+ var clickHandler = function (e) {
+ e.preventDefault()
+ Plugin.call($(this), 'show')
+ }
+
+ $(document)
+ .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
+ .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.3.2
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // AFFIX CLASS DEFINITION
+ // ======================
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, Affix.DEFAULTS, options)
+
+ this.$target = $(this.options.target)
+ .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+ .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
+
+ this.$element = $(element)
+ this.affixed =
+ this.unpin =
+ this.pinnedOffset = null
+
+ this.checkPosition()
+ }
+
+ Affix.VERSION = '3.3.2'
+
+ Affix.RESET = 'affix affix-top affix-bottom'
+
+ Affix.DEFAULTS = {
+ offset: 0,
+ target: window
+ }
+
+ Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ var targetHeight = this.$target.height()
+
+ if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
+
+ if (this.affixed == 'bottom') {
+ if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
+ return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
+ }
+
+ var initializing = this.affixed == null
+ var colliderTop = initializing ? scrollTop : position.top
+ var colliderHeight = initializing ? targetHeight : height
+
+ if (offsetTop != null && scrollTop <= offsetTop) return 'top'
+ if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
+
+ return false
+ }
+
+ Affix.prototype.getPinnedOffset = function () {
+ if (this.pinnedOffset) return this.pinnedOffset
+ this.$element.removeClass(Affix.RESET).addClass('affix')
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ return (this.pinnedOffset = position.top - scrollTop)
+ }
+
+ Affix.prototype.checkPositionWithEventLoop = function () {
+ setTimeout($.proxy(this.checkPosition, this), 1)
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var height = this.$element.height()
+ var offset = this.options.offset
+ var offsetTop = offset.top
+ var offsetBottom = offset.bottom
+ var scrollHeight = $('body').height()
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+ var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
+
+ if (this.affixed != affix) {
+ if (this.unpin != null) this.$element.css('top', '')
+
+ var affixType = 'affix' + (affix ? '-' + affix : '')
+ var e = $.Event(affixType + '.bs.affix')
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+ this.$element
+ .removeClass(Affix.RESET)
+ .addClass(affixType)
+ .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
+ }
+
+ if (affix == 'bottom') {
+ this.$element.offset({
+ top: scrollHeight - height - offsetBottom
+ })
+ }
+ }
+
+
+ // AFFIX PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.affix')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.affix
+
+ $.fn.affix = Plugin
+ $.fn.affix.Constructor = Affix
+
+
+ // AFFIX NO CONFLICT
+ // =================
+
+ $.fn.affix.noConflict = function () {
+ $.fn.affix = old
+ return this
+ }
+
+
+ // AFFIX DATA-API
+ // ==============
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ var data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
+ if (data.offsetTop != null) data.offset.top = data.offsetTop
+
+ Plugin.call($spy, data)
+ })
+ })
+
+}(jQuery);
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js
new file mode 100644
index 000000000..c6d36920b
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$backdrop=this.isShown=null,this.scrollbarWidth=0,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.options.backdrop&&d.adjustBackdrop(),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in").attr("aria-hidden",!1),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$element.find(".modal-dialog").one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a('<div class="modal-backdrop '+e+'" />').prependTo(this.$element).on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.options.backdrop&&this.adjustBackdrop(),this.adjustDialog()},c.prototype.adjustBackdrop=function(){this.$backdrop.css("height",0).css("height",this.$element[0].scrollHeight)},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){this.bodyIsOverflowing=document.body.scrollHeight>document.documentElement.clientHeight,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.3.2",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-m<p.top?"bottom":"right"==h&&k.right+l>p.width?"left":"left"==h&&k.left-l<p.left?"right":h,f.removeClass(n).addClass(h)}var q=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(q,h);var r=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",r).emulateTransitionEnd(c.TRANSITION_DURATION):r()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top=b.top+g,b.left=b.left+h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=this.tip(),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.2",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.2",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.2",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()
+}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.2",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a("body").height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js b/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js
new file mode 100644
index 000000000..bf6aa8060
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js
@@ -0,0 +1,13 @@
+// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
+require('../../js/transition.js')
+require('../../js/alert.js')
+require('../../js/button.js')
+require('../../js/carousel.js')
+require('../../js/collapse.js')
+require('../../js/dropdown.js')
+require('../../js/modal.js')
+require('../../js/tooltip.js')
+require('../../js/popover.js')
+require('../../js/scrollspy.js')
+require('../../js/tab.js')
+require('../../js/affix.js') \ No newline at end of file
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js
new file mode 100644
index 000000000..1e2d4180b
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js
@@ -0,0 +1,45 @@
+/*
+---
+name: MooDialog.Alert
+description: Creates an Alert dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Alert
+...
+*/
+
+
+MooDialog.Alert = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogAlert'
+ },
+
+ initialize: function(msg, options){
+ this.parent(options);
+
+ var okButton = new Element('button', {
+ events: {
+ click: this.close.bind(this)
+ },
+ text: this.options.okText
+ });
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('div.buttons').adopt(okButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ okButton.focus()
+ });
+
+ }
+});
+
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js
new file mode 100644
index 000000000..16f32e290
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js
@@ -0,0 +1,80 @@
+/*
+---
+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('button', {
+ events: {
+ click: function(){
+ button.fn();
+ self.close();
+ }
+ },
+ text: 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/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js
new file mode 100644
index 000000000..d32e30bce
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js
@@ -0,0 +1,21 @@
+/*
+---
+name: MooDialog.Error
+description: Creates an Error dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Error
+...
+*/
+
+
+MooDialog.Error = new Class({
+
+ Extends: MooDialog.Alert,
+
+ options: {
+ textPClass: 'MooDialogError'
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js
new file mode 100644
index 000000000..353d947f5
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js
@@ -0,0 +1,47 @@
+/*
+---
+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: [Core/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));
+
+ this.addEvent('hide', function(){
+ if (this.options.destroyOnHide) this.overlay.overlay.destroy();
+ }.bind(this));
+ },
+
+ onBeforeOpen: function(wrapper){
+ this.overlay.open();
+ 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');
+ }.bind(this));
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js
new file mode 100644
index 000000000..029bf1f09
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js
@@ -0,0 +1,33 @@
+/*
+---
+name: MooDialog.IFrame
+description: Opens an IFrame in a MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.IFrame
+...
+*/
+
+
+MooDialog.IFrame = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ useScrollBar: true
+ },
+
+ initialize: function(url, options){
+ this.parent(options);
+
+ this.setContent(
+ new Element('iframe', {
+ src: url,
+ frameborder: 0,
+ scrolling: this.options.useScrollBar ? 'auto' : 'no'
+ })
+ );
+ if (this.options.autoOpen) this.open();
+ }
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js
new file mode 100644
index 000000000..c693e4a58
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js
@@ -0,0 +1,48 @@
+/*
+---
+name: MooDialog.Prompt
+description: Creates a Prompt dialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: MooDialog
+provides: MooDialog.Prompt
+...
+*/
+
+
+MooDialog.Prompt = new Class({
+
+ Extends: MooDialog,
+
+ options: {
+ okText: 'Ok',
+ focus: true,
+ textPClass: 'MooDialogPrompt',
+ defaultValue: ''
+ },
+
+ initialize: function(msg, fn, options){
+ this.parent(options);
+ if (!fn) fn = function(){};
+
+ var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}),
+ submitButton = new Element('input[type=submit]', {value: this.options.okText}),
+ formEvents = {
+ submit: function(e){
+ e.stop();
+ fn(textInput.get('value'));
+ this.close();
+ }.bind(this)
+ };
+
+ this.setContent(
+ new Element('p.' + this.options.textPClass, {text: msg}),
+ new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton)
+ );
+ if (this.options.autoOpen) this.open();
+
+ if (this.options.focus) this.addEvent('show', function(){
+ textInput.focus();
+ });
+ }
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js
new file mode 100644
index 000000000..7b8eb23c4
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js
@@ -0,0 +1,37 @@
+/*
+---
+name: MooDialog.Request
+description: Loads Data into a Dialog with Request
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [MooDialog, Core/Request.HTML]
+provides: MooDialog.Request
+...
+*/
+
+MooDialog.Request = new Class({
+
+ Extends: MooDialog,
+
+ initialize: function(url, requestOptions, options){
+ this.parent(options);
+ this.requestOptions = requestOptions || {};
+
+ this.addEvent('open', function(){
+ var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){
+ this.setContent(text);
+ }.bind(this)).send({
+ url: url
+ });
+ }.bind(this));
+
+ if (this.options.autoOpen) this.open();
+
+ },
+
+ setRequestOptions: function(options){
+ this.requestOptions = Object.merge(this.requestOptions, options);
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/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/Next/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js
new file mode 100644
index 000000000..35ab19c48
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js
@@ -0,0 +1,137 @@
+/*
+---
+
+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: function(){},
+ onClose: function(){},
+ onHide: function(){},
+ onOpen: function(){},
+ onShow: function(){}
+ */
+ },
+
+ 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,
+ styles: {
+ position: (Browser.ie6) ? 'absolute' : 'fixed',
+ background: this.options.color,
+ left: 0,
+ top: 0,
+ 'z-index': this.options.zIndex,
+ opacity: 0
+ }
+ }).inject(this.container);
+ this.tween = new Fx.Tween(this.overlay, {
+ duration: this.options.duration,
+ link: 'cancel',
+ property: 'opacity'
+ });
+ 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,
+ visibility: 'visible'
+ });
+ return this;
+ },
+
+ tweenComplete: function(){
+ var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide';
+ if (event == 'hide') this.overlay.setStyle('visibility', 'hidden');
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('open');
+ this.tween.start(this.options.opacity);
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('close');
+ this.tween.start(0);
+ 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;
+ }
+
+});
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css
new file mode 100644
index 000000000..c88773ae9
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css
@@ -0,0 +1,95 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+ position: fixed;
+ width: 300px;
+ height: 100px;
+ top: 50%;
+ left: 50%;
+ margin: -150px 0 0 -150px;
+ padding: 10px;
+ 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 .content {
+ height: 100px;
+}
+
+.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 {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ top: -5px;
+ left: -5px;
+
+ background: url(dialog-close.png) no-repeat;
+ display: block;
+ cursor: pointer;
+}
+
+.MooDialog .buttons {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ text-align: right;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ padding-left: 40px;
+ min-height: 40px;
+ background: url(dialog-warning.png) no-repeat;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt {
+ background: url(dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/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/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css
new file mode 100644
index 000000000..e08c2f9fa
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css
@@ -0,0 +1,66 @@
+#nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#nav > li {
+ background: #F5F5F5;
+ border-bottom: 1px solid #aaa;
+}
+
+#nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+
+#nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ width: 136px;
+ 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;
+}
+
+
+#nav .open {
+ display: block;
+}
+
+#nav .close {
+ display: none;
+}
+
+#nav ul li {
+ float: none;
+ padding: 0;
+}
+
+#nav ul li a {
+ width: 130px;
+ _width: 127px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ _float: left;
+ font-weight: normal;
+}
+
+#nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+#nav ul ul {
+ left: 137px;
+ _left: 0;
+ top: 0;
+}
diff --git a/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js
new file mode 100644
index 000000000..e0bea6df6
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js
@@ -0,0 +1,6068 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc
+*/
+/*
+---
+
+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]
+
+...
+*/
+/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/
+(function(){
+
+this.MooTools = {
+ version: '1.5.1',
+ build: '0542c135fdeb7feed7d9917e01447a408f22c876'
+};
+
+// 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: 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: 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: 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: 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';
+ }
+
+ 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.name == '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;
+ window = this.Window = document = 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: 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: 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: 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, currentExpression;
+ 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props){
+ if (props.checked != null) props.defaultChecked = props.checked;
+ if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on';
+ /*<ltIE9>*/ // IE needs the type to be set before changing content of style element
+ if (!canChangeStyleHTML && tag == 'style'){
+ var styleElement = document.createElement('style');
+ styleElement.setAttribute('type', 'text/css');
+ if (props.type) delete props.type;
+ return this.id(styleElement).set(props);
+ }
+ /*</ltIE9>*/
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML){
+ 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];
+ };
+});
+
+/*<ltIE9>*/
+propertySetters.text = (function(setter){
+ return function(node, value){
+ if (node.get('tag') == 'style') node.set('html', value);
+ else node[properties.text] = value;
+ };
+})(propertySetters.text);
+
+propertyGetters.text = (function(getter){
+ return function(node){
+ return (node.get('tag') == 'style') ? node.innerHTML : getter(node);
+ };
+})(propertyGetters.text);
+/*</ltIE9>*/
+
+// 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>*/
+
+/*<ltIE9>*/
+// #2479 - IE8 Cannot set HTML of style element
+var canChangeStyleHTML = (function(){
+ var div = document.createElement('style'),
+ flag = false;
+ try {
+ div.innerHTML = '#justTesing{margin: 0px;}';
+ flag = !!div.innerHTML;
+ } catch(e){}
+ return flag;
+})();
+/*</ltIE9>*/
+
+var input = document.createElement('input'), volatileInputValue, html5InputSupport;
+
+// #2178
+input.value = 't';
+input.type = 'submit';
+volatileInputValue = input.value != 't';
+
+// #2443 - IE throws "Invalid Argument" when trying to use html5 input types
+try {
+ input.type = 'email';
+ html5InputSupport = input.type == 'email';
+} catch(e){}
+
+input = null;
+
+if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){
+ try {
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+ } catch (e){}
+};
+/*</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 classes(this.className).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('');
+
+ /*<ltIE9>*/
+ if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html;
+ else /*</ltIE9>*/this.innerHTML = html;
+ },
+ erase: function(){
+ this.set('html', '');
+ }
+
+};
+
+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){
+
+ /*<ltIE9>*/
+ if (this.styleSheet) return set.call(this, html);
+ /*</ltIE9>*/
+ 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: 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 normalizeWheelSpeed = function(event){
+ var normalized;
+ if (event.wheelDelta){
+ normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12;
+ } else {
+ var rawAmount = event.deltaY || event.detail || 0;
+ normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10);
+ }
+ return normalized;
+}
+
+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 == 'wheel' || 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 == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event);
+ 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: 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
+ wheel: 2, 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, message: 2 //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--;){
+ // the form may have been destroyed, so it won't have the
+ // removeEvent method anymore. In that case the event was
+ // removed as well.
+ if (list.forms[i].removeEvent) 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.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,
+ supportBorderRadius = document.createElement('div').style.borderRadius != null;
+
+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();
+ if (supportBorderRadius && property.indexOf('borderRadius') != -1){
+ return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){
+ return this.style[corner] || '0px';
+ }, this).join(' ');
+ }
+ 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: '@', borderRadius: '@px @px @px @px'
+};
+
+
+
+
+
+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.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 heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
+ widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
+
+var svgCalculateSize = function(el){
+
+ var gCS = window.getComputedStyle(el),
+ bounds = {x: 0, y: 0};
+
+ heightComponents.each(function(css){
+ bounds.y += parseFloat(gCS[css]);
+ });
+ widthComponents.each(function(css){
+ bounds.x += parseFloat(gCS[css]);
+ });
+ return bounds;
+};
+
+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();
+
+ //<ltIE9>
+ // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden.
+ if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight};
+ //</ltIE9>
+
+ // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug.
+ // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985
+ if (this.get('tag') == 'svg') return svgCalculateSize(this);
+
+ var bounds = this.getBoundingClientRect();
+ return {x: bounds.width, y: bounds.height};
+ },
+
+ 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.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: 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: 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: '',
+ withCredentials: false,*/
+ 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;
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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.withCredentials) && '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();
+ if (this.timer){
+ clearTimeout(this.timer);
+ delete 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', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].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) {
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+ }
+ // cleanup scope vars
+ document = window = testElement = null;
+};
+
+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/Next/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js
new file mode 100644
index 000000000..15a277029
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js
@@ -0,0 +1,14345 @@
+/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/
+/*
+Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9
+*/
+/*
+---
+
+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.1',
+ build: '2dd695ba957196ae4b0275a690765d6636a61ccd'
+};
+
+/*
+---
+
+script: Chain.Wait.js
+
+name: Chain.Wait
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Chain
+ - Core/Element
+ - Core/Fx
+ - MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+ var wait = {
+ wait: function(duration){
+ return this.chain(function(){
+ this.callChain.delay(duration == null ? 500 : duration, this);
+ return this;
+ }.bind(this));
+ }
+ };
+
+ Chain.implement(wait);
+
+ if (this.Fx) Fx.implement(wait);
+
+ if (this.Element && Element.implement && this.Fx){
+ Element.implement({
+
+ chains: function(effects){
+ Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
+ effect = this.get(effect);
+ if (!effect) return;
+ effect.setOptions({
+ link:'chain'
+ });
+ }, this);
+ return this;
+ },
+
+ pauseFx: function(duration, effect){
+ this.chains(effect).get(effect || 'tween').wait(duration);
+ return this;
+ }
+
+ });
+ }
+
+})();
+
+/*
+---
+
+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;
+
+};
+
+/*
+---
+
+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);
+});
+
+})();
+
+/*
+---
+
+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,
+ compensateScroll: 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.offsetParent = (function(el){
+ var offsetParent = el.getOffsetParent();
+ var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName);
+ return isBody ? window : document.id(offsetParent);
+ })(this.element);
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+ this.compensateScroll = {start: {}, diff: {}, last: {}};
+
+ 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),
+ scrollListener: this.scrollListener.bind(this)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener);
+ return this;
+ },
+
+ scrollListener: function(){
+
+ if (!this.mouse.start) return;
+ var newScrollValue = this.offsetParent.getScroll();
+
+ if (this.element.getStyle('position') == 'absolute'){
+ var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1);
+ this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1);
+ } else {
+ this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1);
+ }
+ if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1);
+ this.compensateScroll.last = newScrollValue;
+ this.render(this.options);
+ },
+
+ sumValues: function(alpha, beta, op){
+ var sum = {}, options = this.options;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ sum[z] = alpha[z] + beta[z] * op;
+ }
+ return sum;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll();
+ this.compensateScroll.diff = {x: 0, y: 0};
+ this.mouse.start = event.page;
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent;
+ 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(offsetParent);
+ 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 = this.sumValues(event.page, this.compensateScroll.diff, -1);
+
+ this.render(options);
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ render: function(options){
+ 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];
+ }
+ },
+
+ 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);
+ this.mouse.start = null;
+ 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 = {},
+ offsetScroll = offsetParent.getScroll();
+
+ ['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 + offsetScroll.x,
+ top = 0 + offsetScroll.y,
+ right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y;
+
+ 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: 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: Slider.js
+
+name: Slider
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Dimensions
+ - Core/Number
+ - Class.Binds
+ - Drag
+ - Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+ Implements: [Events, Options],
+
+ Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+ options: {/*
+ onTick: function(intPosition){},
+ onMove: function(){},
+ onChange: function(intStep){},
+ onComplete: function(strStep){},*/
+ onTick: function(position){
+ this.setKnobPosition(position);
+ },
+ initialStep: 0,
+ snap: false,
+ offset: 0,
+ range: false,
+ wheel: false,
+ steps: 100,
+ mode: 'horizontal'
+ },
+
+ initialize: function(element, knob, options){
+ this.setOptions(options);
+ options = this.options;
+ this.element = document.id(element);
+ knob = this.knob = document.id(knob);
+ this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0;
+
+ var limit = {},
+ modifiers = {x: false, y: false};
+
+ switch (options.mode){
+ case 'vertical':
+ this.axis = 'y';
+ this.property = 'top';
+ this.offset = 'offsetHeight';
+ break;
+ case 'horizontal':
+ this.axis = 'x';
+ this.property = 'left';
+ this.offset = 'offsetWidth';
+ }
+
+ this.setSliderDimensions();
+ this.setRange(options.range, null, true);
+
+ if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
+ knob.setStyle(this.property, -options.offset);
+ modifiers[this.axis] = this.property;
+ limit[this.axis] = [-options.offset, this.full - options.offset];
+
+ var dragOptions = {
+ snap: 0,
+ limit: limit,
+ modifiers: modifiers,
+ onDrag: this.draggedKnob,
+ onStart: this.draggedKnob,
+ onBeforeStart: (function(){
+ this.isDragging = true;
+ }).bind(this),
+ onCancel: function(){
+ this.isDragging = false;
+ }.bind(this),
+ onComplete: function(){
+ this.isDragging = false;
+ this.draggedKnob();
+ this.end();
+ }.bind(this)
+ };
+ if (options.snap) this.setSnap(dragOptions);
+
+ this.drag = new Drag(knob, dragOptions);
+ if (options.initialStep != null) this.set(options.initialStep, true);
+ this.attach();
+ },
+
+ attach: function(){
+ this.element.addEvent('mousedown', this.clickedElement);
+ if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+ this.drag.attach();
+ return this;
+ },
+
+ detach: function(){
+ this.element.removeEvent('mousedown', this.clickedElement)
+ .removeEvent('mousewheel', this.scrolledElement);
+ this.drag.detach();
+ return this;
+ },
+
+ autosize: function(){
+ this.setSliderDimensions()
+ .setKnobPosition(this.toPosition(this.step));
+ this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
+ if (this.options.snap) this.setSnap();
+ return this;
+ },
+
+ setSnap: function(options){
+ if (!options) options = this.drag.options;
+ options.grid = Math.ceil(this.stepWidth);
+ options.limit[this.axis][1] = this.element[this.offset];
+ return this;
+ },
+
+ setKnobPosition: function(position){
+ if (this.options.snap) position = this.toPosition(this.step);
+ this.knob.setStyle(this.property, position);
+ return this;
+ },
+
+ setSliderDimensions: function(){
+ this.full = this.element.measure(function(){
+ this.half = this.knob[this.offset] / 2;
+ return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
+ }.bind(this));
+ return this;
+ },
+
+ set: function(step, silently){
+ if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+ if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+ this.step = (step).round(this.modulus.decimalLength);
+ if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step));
+ else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end();
+ return this;
+ },
+
+ setRange: function(range, pos, silently){
+ this.min = Array.pick([range[0], 0]);
+ this.max = Array.pick([range[1], this.options.steps]);
+ this.range = this.max - this.min;
+ this.steps = this.options.steps || this.full;
+ var stepSize = this.stepSize = Math.abs(this.range) / this.steps;
+ this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
+ this.setModulus();
+
+ if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently);
+ return this;
+ },
+
+ setModulus: function(){
+ var decimals = ((this.stepSize + '').split('.')[1] || []).length,
+ modulus = 1 + '';
+ while (decimals--) modulus += '0';
+ this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1};
+ },
+
+ clickedElement: function(event){
+ if (this.isDragging || event.target == this.knob) return;
+
+ var dir = this.range < 0 ? -1 : 1,
+ position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+
+ position = position.limit(-this.options.offset, this.full - this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+
+ this.checkStep()
+ .fireEvent('tick', position)
+ .fireEvent('move')
+ .end();
+ },
+
+ scrolledElement: function(event){
+ var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+ this.set(this.step + (mode ? -1 : 1) * this.stepSize);
+ event.stop();
+ },
+
+ draggedKnob: function(){
+ var dir = this.range < 0 ? -1 : 1,
+ position = this.drag.value.now[this.axis];
+
+ position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+ this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength);
+ this.checkStep();
+ this.fireEvent('move');
+ },
+
+ checkStep: function(){
+ var step = this.step;
+ if (this.previousChange != step){
+ this.previousChange = step;
+ this.fireEvent('change', step);
+ }
+ return this;
+ },
+
+ end: function(){
+ var step = this.step;
+ if (this.previousEnd !== step){
+ this.previousEnd = step;
+ this.fireEvent('complete', step + '');
+ }
+ return this;
+ },
+
+ toStep: function(position){
+ var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+ return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step;
+ },
+
+ toPosition: function(step){
+ return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0;
+ }
+
+});
+
+/*
+---
+
+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;
+ }
+
+});
+
+/*
+---
+
+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));
+
+})();
+
+/*
+---
+
+name: Element.Event.Pseudos.Keys
+
+description: Adds functionality fire events if certain keycombinations are pressed
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Element.Event.Pseudos]
+
+provides: [Element.Event.Pseudos.Keys]
+
+...
+*/
+
+(function(){
+
+var keysStoreKey = '$moo:keys-pressed',
+ keysKeyupStoreKey = '$moo:keys-keyup';
+
+
+DOMEvent.definePseudo('keys', function(split, fn, args){
+
+ var event = args[0],
+ keys = [],
+ pressed = this.retrieve(keysStoreKey, []),
+ value = split.value;
+
+ if (value != '+') keys.append(value.replace('++', function(){
+ keys.push('+'); // shift++ and shift+++a
+ return '';
+ }).split('+'));
+ else keys = ['+'];
+
+ pressed.include(event.key);
+
+ if (keys.every(function(key){
+ return pressed.contains(key);
+ })) fn.apply(this, args);
+
+ this.store(keysStoreKey, pressed);
+
+ if (!this.retrieve(keysKeyupStoreKey)){
+ var keyup = function(event){
+ (function(){
+ pressed = this.retrieve(keysStoreKey, []).erase(event.key);
+ this.store(keysStoreKey, pressed);
+ }).delay(0, this); // Fix for IE
+ };
+ this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
+ }
+
+});
+
+DOMEvent.defineKeys({
+ '16': 'shift',
+ '17': 'control',
+ '18': 'alt',
+ '20': 'capslock',
+ '33': 'pageup',
+ '34': 'pagedown',
+ '35': 'end',
+ '36': 'home',
+ '144': 'numlock',
+ '145': 'scrolllock',
+ '186': ';',
+ '187': '=',
+ '188': ',',
+ '190': '.',
+ '191': '/',
+ '192': '`',
+ '219': '[',
+ '220': '\\',
+ '221': ']',
+ '222': "'",
+ '107': '+',
+ '109': '-', // subtract
+ '189': '-' // dash
+})
+
+})();
+
+/*
+---
+
+script: String.Extras.js
+
+name: String.Extras
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Christopher Pitt
+
+requires:
+ - Core/String
+ - Core/Array
+ - MooTools.More
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+
+var special = {
+ 'a': /[àáâãÀåăą]/g,
+ 'A': /[ÀÁÂÃÄÅĂĄ]/g,
+ 'c': /[ćčç]/g,
+ 'C': /[ĆČÇ]/g,
+ 'd': /[ďđ]/g,
+ 'D': /[ĎÐ]/g,
+ 'e': /[Úéêëěę]/g,
+ 'E': /[ÈÉÊËĚĘ]/g,
+ 'g': /[ğ]/g,
+ 'G': /[Ğ]/g,
+ 'i': /[ìíîï]/g,
+ 'I': /[ÌÍÎÏ]/g,
+ 'l': /[ĺğł]/g,
+ 'L': /[ĹĜŁ]/g,
+ 'n': /[ñňń]/g,
+ 'N': /[ÑŇŃ]/g,
+ 'o': /[òóÎõöÞő]/g,
+ 'O': /[ÒÓÔÕÖØ]/g,
+ 'r': /[řŕ]/g,
+ 'R': /[ŘŔ]/g,
+ 's': /[ššş]/g,
+ 'S': /[ŠŞŚ]/g,
+ 't': /[ťţ]/g,
+ 'T': /[ŀŢ]/g,
+ 'u': /[ùúûů̵]/g,
+ 'U': /[ÙÚÛŮÜ]/g,
+ 'y': /[ÿÜ]/g,
+ 'Y': /[ŞÝ]/g,
+ 'z': /[şźŌ]/g,
+ 'Z': /[ŜŹŻ]/g,
+ 'th': /[ß]/g,
+ 'TH': /[Þ]/g,
+ 'dh': /[ð]/g,
+ 'DH': /[Ð]/g,
+ 'ss': /[ß]/g,
+ 'oe': /[œ]/g,
+ 'OE': /[Œ]/g,
+ 'ae': /[Ê]/g,
+ 'AE': /[Æ]/g
+},
+
+tidy = {
+ ' ': /[\xa0\u2002\u2003\u2009]/g,
+ '*': /[\xb7]/g,
+ '\'': /[\u2018\u2019]/g,
+ '"': /[\u201c\u201d]/g,
+ '...': /[\u2026]/g,
+ '-': /[\u2013]/g,
+// '--': /[\u2014]/g,
+ '&raquo;': /[\uFFFD]/g
+},
+
+conversions = {
+ ms: 1,
+ s: 1000,
+ m: 6e4,
+ h: 36e5
+},
+
+findUnits = /(\d*.?\d+)([msh]+)/;
+
+var walk = function(string, replacements){
+ var result = string, key;
+ for (key in replacements) result = result.replace(replacements[key], key);
+ return result;
+};
+
+var getRegexForTag = function(tag, contents){
+ tag = tag || '';
+ var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
+ reg = new RegExp(regstr, "gi");
+ return reg;
+};
+
+String.implement({
+
+ standardize: function(){
+ return walk(this, special);
+ },
+
+ repeat: function(times){
+ return new Array(times + 1).join(this);
+ },
+
+ pad: function(length, str, direction){
+ if (this.length >= length) return this;
+
+ var pad = (str == null ? ' ' : '' + str)
+ .repeat(length - this.length)
+ .substr(0, length - this.length);
+
+ if (!direction || direction == 'right') return this + pad;
+ if (direction == 'left') return pad + this;
+
+ return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+ },
+
+ getTags: function(tag, contents){
+ return this.match(getRegexForTag(tag, contents)) || [];
+ },
+
+ stripTags: function(tag, contents){
+ return this.replace(getRegexForTag(tag, contents), '');
+ },
+
+ tidy: function(){
+ return walk(this, tidy);
+ },
+
+ truncate: function(max, trail, atChar){
+ var string = this;
+ if (trail == null && arguments.length == 1) trail = '
';
+ if (string.length > max){
+ string = string.substring(0, max);
+ if (atChar){
+ var index = string.lastIndexOf(atChar);
+ if (index != -1) string = string.substr(0, index);
+ }
+ if (trail) string += trail;
+ }
+ return string;
+ },
+
+ ms: function(){
+ // "Borrowed" from https://gist.github.com/1503944
+ var units = findUnits.exec(this);
+ if (units == null) return Number(this);
+ return Number(units[1]) * conversions[units[2]];
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Element.Forms.js
+
+name: Element.Forms
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - String.Extras
+ - MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+ tidy: function(){
+ this.set('value', this.get('value').tidy());
+ },
+
+ getTextInRange: function(start, end){
+ return this.get('value').substring(start, end);
+ },
+
+ getSelectedText: function(){
+ if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+ return document.selection.createRange().text;
+ },
+
+ getSelectedRange: function(){
+ if (this.selectionStart != null){
+ return {
+ start: this.selectionStart,
+ end: this.selectionEnd
+ };
+ }
+
+ var pos = {
+ start: 0,
+ end: 0
+ };
+ var range = this.getDocument().selection.createRange();
+ if (!range || range.parentElement() != this) return pos;
+ var duplicate = range.duplicate();
+
+ if (this.type == 'text'){
+ pos.start = 0 - duplicate.moveStart('character', -100000);
+ pos.end = pos.start + range.text.length;
+ } else {
+ var value = this.get('value');
+ var offset = value.length;
+ duplicate.moveToElementText(this);
+ duplicate.setEndPoint('StartToEnd', range);
+ if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+ pos.end = offset - duplicate.text.length;
+ duplicate.setEndPoint('StartToStart', range);
+ pos.start = offset - duplicate.text.length;
+ }
+ return pos;
+ },
+
+ getSelectionStart: function(){
+ return this.getSelectedRange().start;
+ },
+
+ getSelectionEnd: function(){
+ return this.getSelectedRange().end;
+ },
+
+ setCaretPosition: function(pos){
+ if (pos == 'end') pos = this.get('value').length;
+ this.selectRange(pos, pos);
+ return this;
+ },
+
+ getCaretPosition: function(){
+ return this.getSelectedRange().start;
+ },
+
+ selectRange: function(start, end){
+ if (this.setSelectionRange){
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else {
+ var value = this.get('value');
+ var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+ start = value.substr(0, start).replace(/\r/g, '').length;
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', start + diff);
+ range.moveStart('character', start);
+ range.select();
+ }
+ return this;
+ },
+
+ insertAtCursor: function(value, select){
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+ this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+ if (select !== false) this.selectRange(pos.start, pos.start + value.length);
+ else this.setCaretPosition(pos.start + value.length);
+ return this;
+ },
+
+ insertAroundCursor: function(options, select){
+ options = Object.append({
+ before: '',
+ defaultMiddle: '',
+ after: ''
+ }, options);
+
+ var value = this.getSelectedText() || options.defaultMiddle;
+ var pos = this.getSelectedRange();
+ var text = this.get('value');
+
+ if (pos.start == pos.end){
+ this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+ this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+ } else {
+ var current = text.substring(pos.start, pos.end);
+ this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+ var selStart = pos.start + options.before.length;
+ if (select !== false) this.selectRange(selStart, selStart + current.length);
+ else this.setCaretPosition(selStart + text.length);
+ }
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Element.Pin.js
+
+name: Element.Pin
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+ var supportsPositionFixed = false,
+ supportTested = false;
+
+ var testPositionFixed = function(){
+ var test = new Element('div').setStyles({
+ position: 'fixed',
+ top: 0,
+ right: 0
+ }).inject(document.body);
+ supportsPositionFixed = (test.offsetTop === 0);
+ test.dispose();
+ supportTested = true;
+ };
+
+ Element.implement({
+
+ pin: function(enable, forceScroll){
+ if (!supportTested) testPositionFixed();
+ if (this.getStyle('display') == 'none') return this;
+
+ var pinnedPosition,
+ scroll = window.getScroll(),
+ parent,
+ scrollFixer;
+
+ if (enable !== false){
+ pinnedPosition = this.getPosition();
+ if (!this.retrieve('pin:_pinned')) {
+ var currentPosition = {
+ top: pinnedPosition.y - scroll.y,
+ left: pinnedPosition.x - scroll.x,
+ margin: '0px',
+ padding: '0px'
+ };
+
+ if (supportsPositionFixed && !forceScroll){
+ this.setStyle('position', 'fixed').setStyles(currentPosition);
+ } else {
+
+ parent = this.getOffsetParent();
+ var position = this.getPosition(parent),
+ styles = this.getStyles('left', 'top');
+
+ if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
+ if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
+
+ position = {
+ x: styles.left.toInt() - scroll.x,
+ y: styles.top.toInt() - scroll.y
+ };
+
+ scrollFixer = function(){
+ if (!this.retrieve('pin:_pinned')) return;
+ var scroll = window.getScroll();
+ this.setStyles({
+ left: position.x + scroll.x,
+ top: position.y + scroll.y
+ });
+ }.bind(this);
+
+ this.store('pin:_scrollFixer', scrollFixer);
+ window.addEvent('scroll', scrollFixer);
+ }
+ this.store('pin:_pinned', true);
+ }
+
+ } else {
+ if (!this.retrieve('pin:_pinned')) return this;
+
+ parent = this.getParent();
+ var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+
+ pinnedPosition = this.getPosition();
+
+ this.store('pin:_pinned', false);
+ scrollFixer = this.retrieve('pin:_scrollFixer');
+ if (!scrollFixer){
+ this.setStyles({
+ position: 'absolute',
+ top: pinnedPosition.y + scroll.y,
+ left: pinnedPosition.x + scroll.x
+ });
+ } else {
+ this.store('pin:_scrollFixer', null);
+ window.removeEvent('scroll', scrollFixer);
+ }
+ this.removeClass('isPinned');
+ }
+ return this;
+ },
+
+ unpin: function(){
+ return this.pin(false);
+ },
+
+ togglePin: function(){
+ return this.pin(!this.retrieve('pin:_pinned'));
+ }
+
+ });
+
+
+
+})();
+
+/*
+---
+
+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: 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: Elements.From.js
+
+name: Elements.From
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/String
+ - Core/Element
+ - MooTools.More
+
+provides: [Elements.from, Elements.From]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+ if (excludeScripts || excludeScripts == null) text = text.stripScripts();
+
+ var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i);
+
+ if (match){
+ container = new Element('table');
+ var tag = match[1].toLowerCase();
+ if (['td', 'th', 'tr'].contains(tag)){
+ container = new Element('tbody').inject(container);
+ if (tag != 'tr') container = new Element('tr').inject(container);
+ }
+ }
+
+ return (container || new Element('div')).set('html', text).getChildren();
+};
+
+/*
+---
+
+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('&');
+ }
+
+});
+
+/*
+---
+
+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: 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: Form.Request.Append.js
+
+name: Form.Request.Append
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Request
+ - Fx.Reveal
+ - Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+ Extends: Form.Request,
+
+ options: {
+ //onBeforeEffect: function(){},
+ useReveal: true,
+ revealOptions: {},
+ inject: 'bottom'
+ },
+
+ makeRequest: function(){
+ this.request = new Request.HTML(Object.merge({
+ url: this.element.get('action'),
+ method: this.element.get('method') || 'post',
+ spinnerTarget: this.element
+ }, this.options.requestOptions, {
+ evalScripts: false
+ })
+ ).addEvents({
+ success: function(tree, elements, html, javascript){
+ var container;
+ var kids = Elements.from(html);
+ if (kids.length == 1){
+ container = kids[0];
+ } else {
+ container = new Element('div', {
+ styles: {
+ display: 'none'
+ }
+ }).adopt(kids);
+ }
+ container.inject(this.target, this.options.inject);
+ if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
+ this.fireEvent('beforeEffect', container);
+ var finish = function(){
+ this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
+ }.bind(this);
+ if (this.options.useReveal){
+ container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
+ container.reveal();
+ } else {
+ finish();
+ }
+ }.bind(this),
+ failure: function(xhr){
+ this.fireEvent('failure', xhr);
+ }.bind(this)
+ });
+ this.attachReset();
+ }
+
+});
+
+/*
+---
+
+script: Object.Extras.js
+
+name: Object.Extras
+
+description: Extra Object generics, like getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Object.Extras]
+
+...
+*/
+
+(function(){
+
+var defined = function(value){
+ return value != null;
+};
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ getFromPath: function(source, parts){
+ if (typeof parts == 'string') parts = parts.split('.');
+ for (var i = 0, l = parts.length; i < l; i++){
+ if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
+ else return null;
+ }
+ return source;
+ },
+
+ cleanValues: function(object, method){
+ method = method || defined;
+ for (var key in object) if (!method(object[key])){
+ delete object[key];
+ }
+ return object;
+ },
+
+ erase: function(object, key){
+ if (hasOwnProperty.call(object, key)) delete object[key];
+ return object;
+ },
+
+ run: function(object){
+ var args = Array.slice(arguments, 1);
+ for (var key in object) if (object[key].apply){
+ object[key].apply(object, args);
+ }
+ return object;
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Locale.js
+
+name: Locale
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Arian Stolwijk
+
+requires:
+ - Core/Events
+ - Object.Extras
+ - MooTools.More
+
+provides: [Locale, Lang]
+
+...
+*/
+
+(function(){
+
+var current = null,
+ locales = {},
+ inherits = {};
+
+var getSet = function(set){
+ if (instanceOf(set, Locale.Set)) return set;
+ else return locales[set];
+};
+
+var Locale = this.Locale = {
+
+ define: function(locale, set, key, value){
+ var name;
+ if (instanceOf(locale, Locale.Set)){
+ name = locale.name;
+ if (name) locales[name] = locale;
+ } else {
+ name = locale;
+ if (!locales[name]) locales[name] = new Locale.Set(name);
+ locale = locales[name];
+ }
+
+ if (set) locale.define(set, key, value);
+
+
+
+ if (!current) current = locale;
+
+ return locale;
+ },
+
+ use: function(locale){
+ locale = getSet(locale);
+
+ if (locale){
+ current = locale;
+
+ this.fireEvent('change', locale);
+
+
+ }
+
+ return this;
+ },
+
+ getCurrent: function(){
+ return current;
+ },
+
+ get: function(key, args){
+ return (current) ? current.get(key, args) : '';
+ },
+
+ inherit: function(locale, inherits, set){
+ locale = getSet(locale);
+
+ if (locale) locale.inherit(inherits, set);
+ return this;
+ },
+
+ list: function(){
+ return Object.keys(locales);
+ }
+
+};
+
+Object.append(Locale, new Events);
+
+Locale.Set = new Class({
+
+ sets: {},
+
+ inherits: {
+ locales: [],
+ sets: {}
+ },
+
+ initialize: function(name){
+ this.name = name || '';
+ },
+
+ define: function(set, key, value){
+ var defineData = this.sets[set];
+ if (!defineData) defineData = {};
+
+ if (key){
+ if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
+ else defineData[key] = value;
+ }
+ this.sets[set] = defineData;
+
+ return this;
+ },
+
+ get: function(key, args, _base){
+ var value = Object.getFromPath(this.sets, key);
+ if (value != null){
+ var type = typeOf(value);
+ if (type == 'function') value = value.apply(null, Array.from(args));
+ else if (type == 'object') value = Object.clone(value);
+ return value;
+ }
+
+ // get value of inherited locales
+ var index = key.indexOf('.'),
+ set = index < 0 ? key : key.substr(0, index),
+ names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
+ if (!_base) _base = [];
+
+ for (var i = 0, l = names.length; i < l; i++){
+ if (_base.contains(names[i])) continue;
+ _base.include(names[i]);
+
+ var locale = locales[names[i]];
+ if (!locale) continue;
+
+ value = locale.get(key, args, _base);
+ if (value != null) return value;
+ }
+
+ return '';
+ },
+
+ inherit: function(names, set){
+ names = Array.from(names);
+
+ if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
+
+ var l = names.length;
+ while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
+
+ return this;
+ }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Date
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Date]
+
+...
+*/
+
+Locale.define('en-US', 'Date', {
+
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'less than a minute ago',
+ minuteAgo: 'about a minute ago',
+ minutesAgo: '{delta} minutes ago',
+ hourAgo: 'about an hour ago',
+ hoursAgo: 'about {delta} hours ago',
+ dayAgo: '1 day ago',
+ daysAgo: '{delta} days ago',
+ weekAgo: '1 week ago',
+ weeksAgo: '{delta} weeks ago',
+ monthAgo: '1 month ago',
+ monthsAgo: '{delta} months ago',
+ yearAgo: '1 year ago',
+ yearsAgo: '{delta} years ago',
+
+ lessThanMinuteUntil: 'less than a minute from now',
+ minuteUntil: 'about a minute from now',
+ minutesUntil: '{delta} minutes from now',
+ hourUntil: 'about an hour from now',
+ hoursUntil: 'about {delta} hours from now',
+ dayUntil: '1 day from now',
+ daysUntil: '{delta} days from now',
+ weekUntil: '1 week from now',
+ weeksUntil: '{delta} weeks from now',
+ monthUntil: '1 month from now',
+ monthsUntil: '{delta} months from now',
+ yearUntil: '1 year from now',
+ yearsUntil: '{delta} years from now'
+
+});
+
+/*
+---
+
+script: Date.js
+
+name: Date
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - MooTools.More
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+var DateMethods = Date.Methods = {
+ ms: 'Milliseconds',
+ year: 'FullYear',
+ min: 'Minutes',
+ mo: 'Month',
+ sec: 'Seconds',
+ hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+ 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+ 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
+ Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(n, digits, string){
+ if (digits == 1) return n;
+ return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
+};
+
+Date.implement({
+
+ set: function(prop, value){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'set' + DateMethods[prop];
+ if (method && this[method]) this[method](value);
+ return this;
+ }.overloadSetter(),
+
+ get: function(prop){
+ prop = prop.toLowerCase();
+ var method = DateMethods[prop] && 'get' + DateMethods[prop];
+ if (method && this[method]) return this[method]();
+ return null;
+ }.overloadGetter(),
+
+ clone: function(){
+ return new Date(this.get('time'));
+ },
+
+ increment: function(interval, times){
+ interval = interval || 'day';
+ times = times != null ? times : 1;
+
+ switch (interval){
+ case 'year':
+ return this.increment('month', times * 12);
+ case 'month':
+ var d = this.get('date');
+ this.set('date', 1).set('mo', this.get('mo') + times);
+ return this.set('date', d.min(this.get('lastdayofmonth')));
+ case 'week':
+ return this.increment('day', times * 7);
+ case 'day':
+ return this.set('date', this.get('date') + times);
+ }
+
+ if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+ return this.set('time', this.get('time') + times * Date.units[interval]());
+ },
+
+ decrement: function(interval, times){
+ return this.increment(interval, -1 * (times != null ? times : 1));
+ },
+
+ isLeapYear: function(){
+ return Date.isLeapYear(this.get('year'));
+ },
+
+ clearTime: function(){
+ return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+ },
+
+ diff: function(date, resolution){
+ if (typeOf(date) == 'string') date = Date.parse(date);
+
+ return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
+ },
+
+ getLastDayOfMonth: function(){
+ return Date.daysInMonth(this.get('mo'), this.get('year'));
+ },
+
+ getDayOfYear: function(){
+ return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
+ - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+ },
+
+ setDay: function(day, firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
+ var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
+
+ return this.increment('day', day - currentDay);
+ },
+
+ getWeek: function(firstDayOfWeek){
+ if (firstDayOfWeek == null){
+ firstDayOfWeek = Date.getMsg('firstDayOfWeek');
+ if (firstDayOfWeek === '') firstDayOfWeek = 1;
+ }
+
+ var date = this,
+ dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
+ dividend = 0,
+ firstDayOfYear;
+
+ if (firstDayOfWeek == 1){
+ // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
+ var month = date.get('month'),
+ startOfWeek = date.get('date') - dayOfWeek;
+
+ if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
+
+ if (month == 0 && startOfWeek < -2){
+ // Use a date from last year to determine the week
+ date = new Date(date).decrement('day', dayOfWeek);
+ dayOfWeek = 0;
+ }
+
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
+ if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
+ } else {
+ // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
+ // Days in the same week can have a different weeknumber if the week spreads across two years.
+ firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
+ }
+
+ dividend += date.get('dayofyear');
+ dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
+ dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
+
+ return (dividend / 7);
+ },
+
+ getOrdinal: function(day){
+ return Date.getMsg('ordinal', day || this.get('date'));
+ },
+
+ getTimezone: function(){
+ return this.toString()
+ .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+ .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+ },
+
+ getGMTOffset: function(){
+ var off = this.get('timezoneOffset');
+ return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+ },
+
+ setAMPM: function(ampm){
+ ampm = ampm.toUpperCase();
+ var hr = this.get('hr');
+ if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+ else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+ return this;
+ },
+
+ getAMPM: function(){
+ return (this.get('hr') < 12) ? 'AM' : 'PM';
+ },
+
+ parse: function(str){
+ this.set('time', Date.parse(str));
+ return this;
+ },
+
+ isValid: function(date){
+ if (!date) date = this;
+ return typeOf(date) == 'date' && !isNaN(date.valueOf());
+ },
+
+ format: function(format){
+ if (!this.isValid()) return 'invalid date';
+
+ if (!format) format = '%x %X';
+ if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
+ if (typeof format == 'function') return format(this);
+
+ var d = this;
+ return format.replace(/%([a-z%])/gi,
+ function($0, $1){
+ switch ($1){
+ case 'a': return Date.getMsg('days_abbr')[d.get('day')];
+ case 'A': return Date.getMsg('days')[d.get('day')];
+ case 'b': return Date.getMsg('months_abbr')[d.get('month')];
+ case 'B': return Date.getMsg('months')[d.get('month')];
+ case 'c': return d.format('%a %b %d %H:%M:%S %Y');
+ case 'd': return pad(d.get('date'), 2);
+ case 'e': return pad(d.get('date'), 2, ' ');
+ case 'H': return pad(d.get('hr'), 2);
+ case 'I': return pad((d.get('hr') % 12) || 12, 2);
+ case 'j': return pad(d.get('dayofyear'), 3);
+ case 'k': return pad(d.get('hr'), 2, ' ');
+ case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
+ case 'L': return pad(d.get('ms'), 3);
+ case 'm': return pad((d.get('mo') + 1), 2);
+ case 'M': return pad(d.get('min'), 2);
+ case 'o': return d.get('ordinal');
+ case 'p': return Date.getMsg(d.get('ampm'));
+ case 's': return Math.round(d / 1000);
+ case 'S': return pad(d.get('seconds'), 2);
+ case 'T': return d.format('%H:%M:%S');
+ case 'U': return pad(d.get('week'), 2);
+ case 'w': return d.get('day');
+ case 'x': return d.format(Date.getMsg('shortDate'));
+ case 'X': return d.format(Date.getMsg('shortTime'));
+ case 'y': return d.get('year').toString().substr(2);
+ case 'Y': return d.get('year');
+ case 'z': return d.get('GMTOffset');
+ case 'Z': return d.get('Timezone');
+ }
+ return $1;
+ }
+ );
+ },
+
+ toISOString: function(){
+ return this.format('iso8601');
+ }
+
+}).alias({
+ toJSON: 'toISOString',
+ compare: 'diff',
+ strftime: 'format'
+});
+
+// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
+var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+ rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+var formats = {
+ db: '%Y-%m-%d %H:%M:%S',
+ compact: '%Y%m%dT%H%M%S',
+ 'short': '%d %b %H:%M',
+ 'long': '%B %d, %Y %H:%M',
+ rfc822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
+ },
+ rfc2822: function(date){
+ return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
+ },
+ iso8601: function(date){
+ return (
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1, 2) + '-' +
+ pad(date.getUTCDate(), 2) + 'T' +
+ pad(date.getUTCHours(), 2) + ':' +
+ pad(date.getUTCMinutes(), 2) + ':' +
+ pad(date.getUTCSeconds(), 2) + '.' +
+ pad(date.getUTCMilliseconds(), 3) + 'Z'
+ );
+ }
+};
+
+var parsePatterns = [],
+ nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+ var ret = -1,
+ translated = Date.getMsg(type + 's');
+ switch (typeOf(word)){
+ case 'object':
+ ret = translated[word.get(type)];
+ break;
+ case 'number':
+ ret = translated[word];
+ if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
+ break;
+ case 'string':
+ var match = translated.filter(function(name){
+ return this.test(name);
+ }, new RegExp('^' + word, 'i'));
+ if (!match.length) throw new Error('Invalid ' + type + ' string');
+ if (match.length > 1) throw new Error('Ambiguous ' + type);
+ ret = match[0];
+ }
+
+ return (num) ? translated.indexOf(ret) : ret;
+};
+
+var startCentury = 1900,
+ startYear = 70;
+
+Date.extend({
+
+ getMsg: function(key, args){
+ return Locale.get('Date.' + key, args);
+ },
+
+ units: {
+ ms: Function.from(1),
+ second: Function.from(1000),
+ minute: Function.from(60000),
+ hour: Function.from(3600000),
+ day: Function.from(86400000),
+ week: Function.from(608400000),
+ month: function(month, year){
+ var d = new Date;
+ return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
+ },
+ year: function(year){
+ year = year || new Date().get('year');
+ return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+ }
+ },
+
+ daysInMonth: function(month, year){
+ return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+ },
+
+ isLeapYear: function(year){
+ return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+ },
+
+ parse: function(from){
+ var t = typeOf(from);
+ if (t == 'number') return new Date(from);
+ if (t != 'string') return from;
+ from = from.clean();
+ if (!from.length) return null;
+
+ var parsed;
+ parsePatterns.some(function(pattern){
+ var bits = pattern.re.exec(from);
+ return (bits) ? (parsed = pattern.handler(bits)) : false;
+ });
+
+ if (!(parsed && parsed.isValid())){
+ parsed = new Date(nativeParse(from));
+ if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
+ }
+ return parsed;
+ },
+
+ parseDay: function(day, num){
+ return parseWord('day', day, num);
+ },
+
+ parseMonth: function(month, num){
+ return parseWord('month', month, num);
+ },
+
+ parseUTC: function(value){
+ var localDate = new Date(value);
+ var utcSeconds = Date.UTC(
+ localDate.get('year'),
+ localDate.get('mo'),
+ localDate.get('date'),
+ localDate.get('hr'),
+ localDate.get('min'),
+ localDate.get('sec'),
+ localDate.get('ms')
+ );
+ return new Date(utcSeconds);
+ },
+
+ orderIndex: function(unit){
+ return Date.getMsg('dateOrder').indexOf(unit) + 1;
+ },
+
+ defineFormat: function(name, format){
+ formats[name] = format;
+ return this;
+ },
+
+
+
+ defineParser: function(pattern){
+ parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+ return this;
+ },
+
+ defineParsers: function(){
+ Array.flatten(arguments).each(Date.defineParser);
+ return this;
+ },
+
+ define2DigitYearStart: function(year){
+ startYear = year % 100;
+ startCentury = year - startYear;
+ return this;
+ }
+
+}).extend({
+ defineFormats: Date.defineFormat.overloadSetter()
+});
+
+var regexOf = function(type){
+ return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+ return name.substr(0, 3);
+ }).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+ switch (key){
+ case 'T':
+ return '%H:%M:%S';
+ case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+ return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
+ case 'X':
+ return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
+ }
+ return null;
+};
+
+var keys = {
+ d: /[0-2]?[0-9]|3[01]/,
+ H: /[01]?[0-9]|2[0-3]/,
+ I: /0?[1-9]|1[0-2]/,
+ M: /[0-5]?\d/,
+ s: /\d+/,
+ o: /[a-z]*/,
+ p: /[ap]\.?m\.?/,
+ y: /\d{2}|\d{4}/,
+ Y: /\d{4}/,
+ z: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+ currentLanguage = language;
+
+ keys.a = keys.A = regexOf('days');
+ keys.b = keys.B = regexOf('months');
+
+ parsePatterns.each(function(pattern, i){
+ if (pattern.format) parsePatterns[i] = build(pattern.format);
+ });
+};
+
+var build = function(format){
+ if (!currentLanguage) return {format: format};
+
+ var parsed = [];
+ var re = (format.source || format) // allow format to be regex
+ .replace(/%([a-z])/gi,
+ function($0, $1){
+ return replacers($1) || $0;
+ }
+ ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+ .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+ .replace(/%([a-z%])/gi,
+ function($0, $1){
+ var p = keys[$1];
+ if (!p) return $1;
+ parsed.push($1);
+ return '(' + p.source + ')';
+ }
+ ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
+
+ return {
+ format: format,
+ re: new RegExp('^' + re + '$', 'i'),
+ handler: function(bits){
+ bits = bits.slice(1).associate(parsed);
+ var date = new Date().clearTime(),
+ year = bits.y || bits.Y;
+
+ if (year != null) handle.call(date, 'y', year); // need to start in the right year
+ if ('d' in bits) handle.call(date, 'd', 1);
+ if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
+
+ for (var key in bits) handle.call(date, key, bits[key]);
+ return date;
+ }
+ };
+};
+
+var handle = function(key, value){
+ if (!value) return this;
+
+ switch (key){
+ case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+ case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+ case 'd': return this.set('date', value);
+ case 'H': case 'I': return this.set('hr', value);
+ case 'm': return this.set('mo', value - 1);
+ case 'M': return this.set('min', value);
+ case 'p': return this.set('ampm', value.replace(/\./g, ''));
+ case 'S': return this.set('sec', value);
+ case 's': return this.set('ms', ('0.' + value) * 1000);
+ case 'w': return this.set('day', value);
+ case 'Y': return this.set('year', value);
+ case 'y':
+ value = +value;
+ if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+ return this.set('year', value);
+ case 'z':
+ if (value == 'Z') value = '+00';
+ var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+ offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+ return this.set('time', this - offset * 60000);
+ }
+
+ return this;
+};
+
+Date.defineParsers(
+ '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+ '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+ '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+ '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+ '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+ '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+ '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
+ '%T', // %H:%M:%S
+ '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
+);
+
+Locale.addEvent('change', function(language){
+ if (Locale.get('Date')) recompile(language);
+}).fireEvent('change', Locale.getCurrent());
+
+})();
+
+/*
+---
+
+name: Locale.en-US.Form.Validator
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Form.Validator]
+
+...
+*/
+
+Locale.define('en-US', 'FormValidator', {
+
+ required: 'This field is required.',
+ length: 'Please enter {length} characters (you entered {elLength} characters)',
+ minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
+ maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
+ integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+ numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+ digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+ alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
+ alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
+ dateSuchAs: 'Please enter a valid date such as {date}',
+ dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+ email: 'Please enter a valid email address. For example "fred@domain.com".',
+ url: 'Please enter a valid URL such as http://www.example.com.',
+ currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
+ oneRequired: 'Please enter something for at least one of these inputs.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Warning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'There can be no spaces in this input.',
+ reqChkByNode: 'No items are selected.',
+ requiredChk: 'This field is required.',
+ reqChkByName: 'Please select a {label}.',
+ match: 'This field needs to match the {matchName} field',
+ startDate: 'the start date',
+ endDate: 'the end date',
+ currentDate: 'the current date',
+ afterDate: 'The date should be the same or after {label}.',
+ beforeDate: 'The date should be the same or before {label}.',
+ startMonth: 'Please select a start month',
+ sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+ creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+
+/*
+---
+
+script: Form.Validator.js
+
+name: Form.Validator
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Delegation
+ - Core/Slick.Finder
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/JSON
+ - Locale
+ - Class.Binds
+ - Date
+ - Element.Forms
+ - Locale.en-US.Form.Validator
+ - Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = this.InputValidator = new Class({
+
+ Implements: [Options],
+
+ options: {
+ errorMsg: 'Validation failed.',
+ test: Function.from(true)
+ },
+
+ initialize: function(className, options){
+ this.setOptions(options);
+ this.className = className;
+ },
+
+ test: function(field, props){
+ field = document.id(field);
+ return (field) ? this.options.test(field, props || this.getProps(field)) : false;
+ },
+
+ getError: function(field, props){
+ field = document.id(field);
+ var err = this.options.errorMsg;
+ if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
+ return err;
+ },
+
+ getProps: function(field){
+ field = document.id(field);
+ return (field) ? field.get('validatorProps') : {};
+ }
+
+});
+
+Element.Properties.validators = {
+
+ get: function(){
+ return (this.get('data-validators') || this.className).clean().split(' ');
+ }
+
+};
+
+Element.Properties.validatorProps = {
+
+ set: function(props){
+ return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
+ },
+
+ get: function(props){
+ if (props) this.set(props);
+ if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
+ if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
+ try {
+ this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false));
+ }catch(e){
+ return {};
+ }
+ } else {
+ var vals = this.get('validators').filter(function(cls){
+ return cls.test(':');
+ });
+ if (!vals.length){
+ this.store('$moo:validatorProps', {});
+ } else {
+ props = {};
+ vals.each(function(cls){
+ var split = cls.split(':');
+ if (split[1]){
+ try {
+ props[split[0]] = JSON.decode(split[1], false);
+ } catch(e){}
+ }
+ });
+ this.store('$moo:validatorProps', props);
+ }
+ }
+ return this.retrieve('$moo:validatorProps');
+ }
+
+};
+
+Form.Validator = new Class({
+
+ Implements: [Options, Events],
+
+ options: {/*
+ onFormValidate: function(isValid, form, event){},
+ onElementValidate: function(isValid, field, className, warn){},
+ onElementPass: function(field){},
+ onElementFail: function(field, validatorsFailed){}, */
+ fieldSelectors: 'input, select, textarea',
+ ignoreHidden: true,
+ ignoreDisabled: true,
+ useTitles: false,
+ evaluateOnSubmit: true,
+ evaluateFieldsOnBlur: true,
+ evaluateFieldsOnChange: true,
+ serial: true,
+ stopOnFailure: true,
+ warningPrefix: function(){
+ return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+ },
+ errorPrefix: function(){
+ return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+ }
+ },
+
+ initialize: function(form, options){
+ this.setOptions(options);
+ this.element = document.id(form);
+ this.warningPrefix = Function.from(this.options.warningPrefix)();
+ this.errorPrefix = Function.from(this.options.errorPrefix)();
+ this._bound = {
+ onSubmit: this.onSubmit.bind(this),
+ blurOrChange: function(event, field){
+ this.validationMonitor(field, true);
+ }.bind(this)
+ };
+ this.enable();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ getFields: function(){
+ return (this.fields = this.element.getElements(this.options.fieldSelectors));
+ },
+
+ enable: function(){
+ this.element.store('validator', this);
+ if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit);
+ if (this.options.evaluateFieldsOnBlur){
+ this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ if (this.options.evaluateFieldsOnChange){
+ this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange);
+ }
+ },
+
+ disable: function(){
+ this.element.eliminate('validator');
+ this.element.removeEvents({
+ submit: this._bound.onSubmit,
+ 'blur:relay(input,select,textarea)': this._bound.blurOrChange,
+ 'change:relay(input,select,textarea)': this._bound.blurOrChange
+ });
+ },
+
+ validationMonitor: function(){
+ clearTimeout(this.timer);
+ this.timer = this.validateField.delay(50, this, arguments);
+ },
+
+ onSubmit: function(event){
+ if (this.validate(event)) this.reset();
+ },
+
+ reset: function(){
+ this.getFields().each(this.resetField, this);
+ return this;
+ },
+
+ validate: function(event){
+ var result = this.getFields().map(function(field){
+ return this.validateField(field, true);
+ }, this).every(function(v){
+ return v;
+ });
+ this.fireEvent('formValidate', [result, this.element, event]);
+ if (this.options.stopOnFailure && !result && event) event.preventDefault();
+ return result;
+ },
+
+ validateField: function(field, force){
+ if (this.paused) return true;
+ field = document.id(field);
+ var passed = !field.hasClass('validation-failed');
+ var failed, warned;
+ if (this.options.serial && !force){
+ failed = this.element.getElement('.validation-failed');
+ warned = this.element.getElement('.warning');
+ }
+ if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+ var validationTypes = field.get('validators');
+ var validators = validationTypes.some(function(cn){
+ return this.getValidator(cn);
+ }, this);
+ var validatorsFailed = [];
+ validationTypes.each(function(className){
+ if (className && !this.test(className, field)) validatorsFailed.include(className);
+ }, this);
+ passed = validatorsFailed.length === 0;
+ if (validators && !this.hasValidator(field, 'warnOnly')){
+ if (passed){
+ field.addClass('validation-passed').removeClass('validation-failed');
+ this.fireEvent('elementPass', [field]);
+ } else {
+ field.addClass('validation-failed').removeClass('validation-passed');
+ this.fireEvent('elementFail', [field, validatorsFailed]);
+ }
+ }
+ if (!warned){
+ var warnings = validationTypes.some(function(cn){
+ if (cn.test('^warn'))
+ return this.getValidator(cn.replace(/^warn-/,''));
+ else return null;
+ }, this);
+ field.removeClass('warning');
+ var warnResult = validationTypes.map(function(cn){
+ if (cn.test('^warn'))
+ return this.test(cn.replace(/^warn-/,''), field, true);
+ else return null;
+ }, this);
+ }
+ }
+ return passed;
+ },
+
+ test: function(className, field, warn){
+ field = document.id(field);
+ if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+ var validator = this.getValidator(className);
+ if (warn != null) warn = false;
+ if (this.hasValidator(field, 'warnOnly')) warn = true;
+ var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true);
+ if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+ if (warn) return true;
+ return isValid;
+ },
+
+ hasValidator: function(field, value){
+ return field.get('validators').contains(value);
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (field){
+ field.get('validators').each(function(className){
+ if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+ field.removeClass('validation-failed');
+ field.removeClass('warning');
+ field.removeClass('validation-passed');
+ }, this);
+ }
+ return this;
+ },
+
+ stop: function(){
+ this.paused = true;
+ return this;
+ },
+
+ start: function(){
+ this.paused = false;
+ return this;
+ },
+
+ ignoreField: function(field, warn){
+ field = document.id(field);
+ if (field){
+ this.enforceField(field);
+ if (warn) field.addClass('warnOnly');
+ else field.addClass('ignoreValidation');
+ }
+ return this;
+ },
+
+ enforceField: function(field){
+ field = document.id(field);
+ if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+ return this;
+ }
+
+});
+
+Form.Validator.getMsg = function(key){
+ return Locale.get('FormValidator.' + key);
+};
+
+Form.Validator.adders = {
+
+ validators:{},
+
+ add : function(className, options){
+ this.validators[className] = new InputValidator(className, options);
+ //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+ //extend these validators into it
+ //this allows validators to be global and/or per instance
+ if (!this.initialize){
+ this.implement({
+ validators: this.validators
+ });
+ }
+ },
+
+ addAllThese : function(validators){
+ Array.from(validators).each(function(validator){
+ this.add(validator[0], validator[1]);
+ }, this);
+ },
+
+ getValidator: function(className){
+ return this.validators[className.split(':')[0]];
+ }
+
+};
+
+Object.append(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+ errorMsg: false,
+ test: function(element){
+ if (element.type == 'select-one' || element.type == 'select')
+ return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+ else
+ return ((element.get('value') == null) || (element.get('value').length == 0));
+ }
+
+});
+
+Form.Validator.addAllThese([
+
+ ['required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element){
+ return !Form.Validator.getValidator('IsEmpty').test(element);
+ }
+ }],
+
+ ['length', {
+ errorMsg: function(element, props){
+ if (typeOf(props.length) != 'null')
+ return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
+ else return true;
+ }
+ }],
+
+ ['minLength', {
+ errorMsg: function(element, props){
+ if (typeOf(props.minLength) != 'null')
+ return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
+ else return true;
+ }
+ }],
+
+ ['maxLength', {
+ errorMsg: function(element, props){
+ //props is {maxLength:10}
+ if (typeOf(props.maxLength) != 'null')
+ return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
+ else return '';
+ },
+ test: function(element, props){
+ return element.get('value').length <= (props.maxLength || 10000);
+ }
+ }],
+
+ ['validate-integer', {
+ errorMsg: Form.Validator.getMsg.pass('integer'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-numeric', {
+ errorMsg: Form.Validator.getMsg.pass('numeric'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) ||
+ (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-digits', {
+ errorMsg: Form.Validator.getMsg.pass('digits'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+ }
+ }],
+
+ ['validate-alpha', {
+ errorMsg: Form.Validator.getMsg.pass('alpha'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-alphanum', {
+ errorMsg: Form.Validator.getMsg.pass('alphanum'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-date', {
+ errorMsg: function(element, props){
+ if (Date.parse){
+ var format = props.dateFormat || '%x';
+ return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+ } else {
+ return Form.Validator.getMsg('dateInFormatMDY');
+ }
+ },
+ test: function(element, props){
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+ var dateLocale = Locale.get('Date'),
+ dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'),
+ value = element.get('value'),
+ wordsInValue = value.match(/[a-z]+/gi);
+
+ if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
+
+ var date = Date.parse(value);
+ if (!date) return false;
+
+ var format = props.dateFormat || '%x',
+ formatted = date.format(format);
+ if (formatted != 'invalid date') element.set('value', formatted);
+ return date.isValid();
+ }
+ }],
+
+ ['validate-email', {
+ errorMsg: Form.Validator.getMsg.pass('email'),
+ test: function(element){
+ /*
+ var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
+ local = '(?:' + chars + '\\.?){0,63}' + chars,
+
+ label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
+ hostname = '(?:' + label + '\\.)*' + label;
+
+ octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
+ ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
+
+ domain = '(?:' + hostname + '|' + ipv4 + ')';
+
+ var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
+ */
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-url', {
+ errorMsg: Form.Validator.getMsg.pass('url'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+ }
+ }],
+
+ ['validate-currency-dollar', {
+ errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+ test: function(element){
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+ }],
+
+ ['validate-one-required', {
+ errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+ test: function(element, props){
+ var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
+ return p.getElements('input').some(function(el){
+ if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+ return el.get('value');
+ });
+ }
+ }]
+
+]);
+
+Element.Properties.validator = {
+
+ set: function(options){
+ this.get('validator').setOptions(options);
+ },
+
+ get: function(){
+ var validator = this.retrieve('validator');
+ if (!validator){
+ validator = new Form.Validator(this);
+ this.store('validator', validator);
+ }
+ return validator;
+ }
+
+};
+
+Element.implement({
+
+ validate: function(options){
+ if (options) this.set('validator', options);
+ return this.get('validator').validate();
+ }
+
+});
+
+
+
+
+
+
+/*
+---
+
+script: Form.Validator.Extras.js
+
+name: Form.Validator.Extras
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+ ['validate-enforce-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+ if (element.checked){
+ fv.enforceField(item);
+ } else {
+ fv.ignoreField(item);
+ fv.resetField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-ignore-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+ if (element.checked){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ } else {
+ fv.enforceField(item);
+ }
+ });
+ return true;
+ }
+ }],
+
+ ['validate-nospace', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('noSpace');
+ },
+ test: function(element, props){
+ return !element.get('value').test(/\s/);
+ }
+ }],
+
+ ['validate-toggle-oncheck', {
+ test: function(element, props){
+ var fv = element.getParent('form').retrieve('validator');
+ if (!fv) return true;
+ var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+ if (!element.checked){
+ eleArr.each(function(item){
+ fv.ignoreField(item);
+ fv.resetField(item);
+ });
+ } else {
+ eleArr.each(function(item){
+ fv.enforceField(item);
+ });
+ }
+ return true;
+ }
+ }],
+
+ ['validate-reqchk-bynode', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('reqChkByNode');
+ },
+ test: function(element, props){
+ return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+ return item.checked;
+ });
+ }
+ }],
+
+ ['validate-required-check', {
+ errorMsg: function(element, props){
+ return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+ },
+ test: function(element, props){
+ return !!element.checked;
+ }
+ }],
+
+ ['validate-reqchk-byname', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+ },
+ test: function(element, props){
+ var grpName = props.groupName || element.get('name');
+ var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+ return item.checked;
+ });
+ var fv = element.getParent('form').retrieve('validator');
+ if (oneCheckedItem && fv) fv.resetField(element);
+ return oneCheckedItem;
+ }
+ }],
+
+ ['validate-match', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+ },
+ test: function(element, props){
+ var eleVal = element.get('value');
+ var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+ return eleVal && matchVal ? eleVal == matchVal : true;
+ }
+ }],
+
+ ['validate-after-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('afterDate').substitute({
+ label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+ var end = Date.parse(element.get('value'));
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-before-date', {
+ errorMsg: function(element, props){
+ return Form.Validator.getMsg('beforeDate').substitute({
+ label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+ });
+ },
+ test: function(element, props){
+ var start = Date.parse(element.get('value'));
+ var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+ return end && start ? end >= start : true;
+ }
+ }],
+
+ ['validate-custom-required', {
+ errorMsg: function(){
+ return Form.Validator.getMsg('required');
+ },
+ test: function(element, props){
+ return element.get('value') != props.emptyValue;
+ }
+ }],
+
+ ['validate-same-month', {
+ errorMsg: function(element, props){
+ var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+ var eleVal = element.get('value');
+ if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+ },
+ test: function(element, props){
+ var d1 = Date.parse(element.get('value'));
+ var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+ return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+ }
+ }],
+
+
+ ['validate-cc-num', {
+ errorMsg: function(element){
+ var ccNum = element.get('value').replace(/[^0-9]/g, '');
+ return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+ },
+ test: function(element){
+ // required is a different test
+ if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+
+ // Clean number value
+ var ccNum = element.get('value');
+ ccNum = ccNum.replace(/[^0-9]/g, '');
+
+ var valid_type = false;
+
+ if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+ else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+ else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+ else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover';
+ else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club';
+
+ if (valid_type){
+ var sum = 0;
+ var cur = 0;
+
+ for (var i=ccNum.length-1; i>=0; --i){
+ cur = ccNum.charAt(i).toInt();
+ if (cur == 0) continue;
+
+ if ((ccNum.length-i) % 2 == 0) cur += cur;
+ if (cur > 9){
+ cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
+ }
+
+ sum += cur;
+ }
+ if ((sum % 10) == 0) return true;
+ }
+
+ var chunks = '';
+ while (ccNum != ''){
+ chunks += ' ' + ccNum.substr(0,4);
+ ccNum = ccNum.substr(4);
+ }
+
+ element.getParent('form').retrieve('validator').ignoreField(element);
+ element.set('value', chunks.clean());
+ element.getParent('form').retrieve('validator').enforceField(element);
+ return false;
+ }
+ }]
+
+
+]);
+
+/*
+---
+
+script: Form.Validator.Inline.js
+
+name: Form.Validator.Inline
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+ Extends: Form.Validator,
+
+ options: {
+ showError: function(errorElement){
+ if (errorElement.reveal) errorElement.reveal();
+ else errorElement.setStyle('display', 'block');
+ },
+ hideError: function(errorElement){
+ if (errorElement.dissolve) errorElement.dissolve();
+ else errorElement.setStyle('display', 'none');
+ },
+ scrollToErrorsOnSubmit: true,
+ scrollToErrorsOnBlur: false,
+ scrollToErrorsOnChange: false,
+ scrollFxOptions: {
+ transition: 'quad:out',
+ offset: {
+ y: -20
+ }
+ }
+ },
+
+ initialize: function(form, options){
+ this.parent(form, options);
+ this.addEvent('onElementValidate', function(isValid, field, className, warn){
+ var validator = this.getValidator(className);
+ if (!isValid && validator.getError(field)){
+ if (warn) field.addClass('warning');
+ var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+ this.insertAdvice(advice, field);
+ this.showAdvice(className, field);
+ } else {
+ this.hideAdvice(className, field);
+ }
+ });
+ },
+
+ makeAdvice: function(className, field, error, warn){
+ var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
+ errorMsg += (this.options.useTitles) ? field.title || error:error;
+ var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+ var advice = this.getAdvice(className, field);
+ if (advice){
+ advice = advice.set('html', errorMsg);
+ } else {
+ advice = new Element('div', {
+ html: errorMsg,
+ styles: { display: 'none' },
+ id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
+ }).addClass(cssClass);
+ }
+ field.store('$moo:advice-' + className, advice);
+ return advice;
+ },
+
+ getFieldId : function(field){
+ return field.id ? field.id : field.id = 'input_' + field.name;
+ },
+
+ showAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (
+ advice &&
+ !field.retrieve('$moo:' + this.getPropName(className)) &&
+ (
+ advice.getStyle('display') == 'none' ||
+ advice.getStyle('visibility') == 'hidden' ||
+ advice.getStyle('opacity') == 0
+ )
+ ){
+ field.store('$moo:' + this.getPropName(className), true);
+ this.options.showError(advice);
+ this.fireEvent('showAdvice', [field, advice, className]);
+ }
+ },
+
+ hideAdvice: function(className, field){
+ var advice = this.getAdvice(className, field);
+ if (advice && field.retrieve('$moo:' + this.getPropName(className))){
+ field.store('$moo:' + this.getPropName(className), false);
+ this.options.hideError(advice);
+ this.fireEvent('hideAdvice', [field, advice, className]);
+ }
+ },
+
+ getPropName: function(className){
+ return 'advice' + className;
+ },
+
+ resetField: function(field){
+ field = document.id(field);
+ if (!field) return this;
+ this.parent(field);
+ field.get('validators').each(function(className){
+ this.hideAdvice(className, field);
+ }, this);
+ return this;
+ },
+
+ getAllAdviceMessages: function(field, force){
+ var advice = [];
+ if (field.hasClass('ignoreValidation') && !force) return advice;
+ var validators = field.get('validators').some(function(cn){
+ var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+ if (warner) cn = cn.replace(/^warn-/, '');
+ var validator = this.getValidator(cn);
+ if (!validator) return;
+ advice.push({
+ message: validator.getError(field),
+ warnOnly: warner,
+ passed: validator.test(),
+ validator: validator
+ });
+ }, this);
+ return advice;
+ },
+
+ getAdvice: function(className, field){
+ return field.retrieve('$moo:advice-' + className);
+ },
+
+ insertAdvice: function(advice, field){
+ //Check for error position prop
+ var props = field.get('validatorProps');
+ //Build advice
+ if (!props.msgPos || !document.id(props.msgPos)){
+ if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+ else advice.inject(document.id(field), 'after');
+ } else {
+ document.id(props.msgPos).grab(advice);
+ }
+ },
+
+ validateField: function(field, force, scroll){
+ var result = this.parent(field, force);
+ if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
+ var failed = document.id(this).getElement('.validation-failed');
+ var par = document.id(this).getParent();
+ while (par != document.body && par.getScrollSize().y == par.getSize().y){
+ par = par.getParent();
+ }
+ var fx = par.retrieve('$moo:fvScroller');
+ if (!fx && window.Fx && Fx.Scroll){
+ fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+ par.store('$moo:fvScroller', fx);
+ }
+ if (failed){
+ if (fx) fx.toElement(failed);
+ else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+ }
+ }
+ return result;
+ },
+
+ watchFields: function(fields){
+ fields.each(function(el){
+ if (this.options.evaluateFieldsOnBlur){
+ el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
+ }
+ if (this.options.evaluateFieldsOnChange){
+ el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
+ }
+ }, this);
+ }
+
+});
+
+/*
+---
+
+script: OverText.js
+
+name: OverText
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Class.Occlude
+ - Element.Position
+ - Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+ options: {/*
+ textOverride: null,
+ onFocus: function(){},
+ onTextHide: function(textEl, inputEl){},
+ onTextShow: function(textEl, inputEl){}, */
+ element: 'label',
+ labelClass: 'overTxtLabel',
+ positionOptions: {
+ position: 'upperLeft',
+ edge: 'upperLeft',
+ offset: {
+ x: 4,
+ y: 2
+ }
+ },
+ poll: false,
+ pollInterval: 250,
+ wrap: false
+ },
+
+ property: 'OverText',
+
+ initialize: function(element, options){
+ element = this.element = document.id(element);
+
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+
+ this.attach(element);
+ OverText.instances.push(this);
+
+ if (this.options.poll) this.poll();
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ attach: function(){
+ var element = this.element,
+ options = this.options,
+ value = options.textOverride || element.get('alt') || element.get('title');
+
+ if (!value) return this;
+
+ var text = this.text = new Element(options.element, {
+ 'class': options.labelClass,
+ styles: {
+ lineHeight: 'normal',
+ position: 'absolute',
+ cursor: 'text'
+ },
+ html: value,
+ events: {
+ click: this.hide.pass(options.element == 'label', this)
+ }
+ }).inject(element, 'after');
+
+ if (options.element == 'label'){
+ if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
+ text.set('for', element.get('id'));
+ }
+
+ if (options.wrap){
+ this.textHolder = new Element('div.overTxtWrapper', {
+ styles: {
+ lineHeight: 'normal',
+ position: 'relative'
+ }
+ }).grab(text).inject(element, 'before');
+ }
+
+ return this.enable();
+ },
+
+ destroy: function(){
+ this.element.eliminate(this.property); // Class.Occlude storage
+ this.disable();
+ if (this.text) this.text.destroy();
+ if (this.textHolder) this.textHolder.destroy();
+ return this;
+ },
+
+ disable: function(){
+ this.element.removeEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.removeEvent('resize', this.reposition);
+ this.hide(true, true);
+ return this;
+ },
+
+ enable: function(){
+ this.element.addEvents({
+ focus: this.focus,
+ blur: this.assert,
+ change: this.assert
+ });
+ window.addEvent('resize', this.reposition);
+ this.reposition();
+ return this;
+ },
+
+ wrap: function(){
+ if (this.options.element == 'label'){
+ if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
+ this.text.set('for', this.element.get('id'));
+ }
+ },
+
+ startPolling: function(){
+ this.pollingPaused = false;
+ return this.poll();
+ },
+
+ poll: function(stop){
+ //start immediately
+ //pause on focus
+ //resumeon blur
+ if (this.poller && !stop) return this;
+ if (stop){
+ clearInterval(this.poller);
+ } else {
+ this.poller = (function(){
+ if (!this.pollingPaused) this.assert(true);
+ }).periodical(this.options.pollInterval, this);
+ }
+
+ return this;
+ },
+
+ stopPolling: function(){
+ this.pollingPaused = true;
+ return this.poll(true);
+ },
+
+ focus: function(){
+ if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
+ return this.hide();
+ },
+
+ hide: function(suppressFocus, force){
+ if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+ this.text.hide();
+ this.fireEvent('textHide', [this.text, this.element]);
+ this.pollingPaused = true;
+ if (!suppressFocus){
+ try {
+ this.element.fireEvent('focus');
+ this.element.focus();
+ } catch(e){} //IE barfs if you call focus on hidden elements
+ }
+ }
+ return this;
+ },
+
+ show: function(){
+ if (document.id(this.text) && !this.text.isDisplayed()){
+ this.text.show();
+ this.reposition();
+ this.fireEvent('textShow', [this.text, this.element]);
+ this.pollingPaused = false;
+ }
+ return this;
+ },
+
+ test: function(){
+ return !this.element.get('value');
+ },
+
+ assert: function(suppressFocus){
+ return this[this.test() ? 'show' : 'hide'](suppressFocus);
+ },
+
+ reposition: function(){
+ this.assert(true);
+ if (!this.element.isVisible()) return this.stopPolling().hide();
+ if (this.text && this.test()){
+ this.text.position(Object.merge(this.options.positionOptions, {
+ relativeTo: this.element
+ }));
+ }
+ return this;
+ }
+
+});
+
+OverText.instances = [];
+
+Object.append(OverText, {
+
+ each: function(fn){
+ return OverText.instances.each(function(ot, i){
+ if (ot.element && ot.text) fn.call(OverText, ot, i);
+ });
+ },
+
+ update: function(){
+
+ return OverText.each(function(ot){
+ return ot.reposition();
+ });
+
+ },
+
+ hideAll: function(){
+
+ return OverText.each(function(ot){
+ return ot.hide(true, true);
+ });
+
+ },
+
+ showAll: function(){
+ return OverText.each(function(ot){
+ return ot.show();
+ });
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Elements.js
+
+name: Fx.Elements
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx.CSS
+ - MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(elements, options){
+ this.elements = this.subject = $$(elements);
+ this.parent(options);
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+
+ for (var i in from){
+ var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+ for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+ }
+
+ return now;
+ },
+
+ set: function(now){
+ for (var i in now){
+ if (!this.elements[i]) continue;
+
+ var iNow = now[i];
+ for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+ }
+
+ return this;
+ },
+
+ start: function(obj){
+ if (!this.check(obj)) return this;
+ var from = {}, to = {};
+
+ for (var i in obj){
+ if (!this.elements[i]) continue;
+
+ var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+
+ for (var p in iProps){
+ var parsed = this.prepare(this.elements[i], p, iProps[p]);
+ iFrom[p] = parsed.from;
+ iTo[p] = parsed.to;
+ }
+ }
+
+ return this.parent(from, to);
+ }
+
+});
+
+/*
+---
+
+script: Fx.Accordion.js
+
+name: Fx.Accordion
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {/*
+ onActive: function(toggler, section){},
+ onBackground: function(toggler, section){},*/
+ fixedHeight: false,
+ fixedWidth: false,
+ display: 0,
+ show: false,
+ height: true,
+ width: false,
+ opacity: true,
+ alwaysHide: false,
+ trigger: 'click',
+ initialDisplayFx: true,
+ resetHeight: true
+ },
+
+ initialize: function(){
+ var defined = function(obj){
+ return obj != null;
+ };
+
+ var params = Array.link(arguments, {
+ 'container': Type.isElement, //deprecated
+ 'options': Type.isObject,
+ 'togglers': defined,
+ 'elements': defined
+ });
+ this.parent(params.elements, params.options);
+
+ var options = this.options,
+ togglers = this.togglers = $$(params.togglers);
+
+ this.previous = -1;
+ this.internalChain = new Chain();
+
+ if (options.alwaysHide) this.options.link = 'chain';
+
+ if (options.show || this.options.show === 0){
+ options.display = false;
+ this.previous = options.show;
+ }
+
+ if (options.start){
+ options.display = false;
+ options.show = false;
+ }
+
+ var effects = this.effects = {};
+
+ if (options.opacity) effects.opacity = 'fullOpacity';
+ if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+ if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+
+ for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
+
+ this.elements.each(function(el, i){
+ if (options.show === i){
+ this.fireEvent('active', [togglers[i], el]);
+ } else {
+ for (var fx in effects) el.setStyle(fx, 0);
+ }
+ }, this);
+
+ if (options.display || options.display === 0 || options.initialDisplayFx === false){
+ this.display(options.display, options.initialDisplayFx);
+ }
+
+ if (options.fixedHeight !== false) options.resetHeight = false;
+ this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+ },
+
+ addSection: function(toggler, element){
+ toggler = document.id(toggler);
+ element = document.id(element);
+ this.togglers.include(toggler);
+ this.elements.include(element);
+
+ var togglers = this.togglers,
+ options = this.options,
+ test = togglers.contains(toggler),
+ idx = togglers.indexOf(toggler),
+ displayer = this.display.pass(idx, this);
+
+ toggler.store('accordion:display', displayer)
+ .addEvent(options.trigger, displayer);
+
+ if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+ if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+
+ element.fullOpacity = 1;
+ if (options.fixedWidth) element.fullWidth = options.fixedWidth;
+ if (options.fixedHeight) element.fullHeight = options.fixedHeight;
+ element.setStyle('overflow', 'hidden');
+
+ if (!test) for (var fx in this.effects){
+ element.setStyle(fx, 0);
+ }
+ return this;
+ },
+
+ removeSection: function(toggler, displayIndex){
+ var togglers = this.togglers,
+ idx = togglers.indexOf(toggler),
+ element = this.elements[idx];
+
+ var remover = function(){
+ togglers.erase(toggler);
+ this.elements.erase(element);
+ this.detach(toggler);
+ }.bind(this);
+
+ if (this.now == idx || displayIndex != null){
+ this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
+ } else {
+ remover();
+ }
+ return this;
+ },
+
+ detach: function(toggler){
+ var remove = function(toggler){
+ toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+ }.bind(this);
+
+ if (!toggler) this.togglers.each(remove);
+ else remove(toggler);
+ return this;
+ },
+
+ display: function(index, useFx){
+ if (!this.check(index, useFx)) return this;
+
+ var obj = {},
+ elements = this.elements,
+ options = this.options,
+ effects = this.effects;
+
+ if (useFx == null) useFx = true;
+ if (typeOf(index) == 'element') index = elements.indexOf(index);
+ if (index == this.current && !options.alwaysHide) return this;
+
+ if (options.resetHeight){
+ var prev = elements[this.current];
+ if (prev && !this.selfHidden){
+ for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
+ }
+ }
+
+ if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this;
+
+ if (this.current != null) this.previous = this.current;
+ this.current = index;
+ this.selfHidden = false;
+
+ elements.each(function(el, i){
+ obj[i] = {};
+ var hide;
+ if (i != index){
+ hide = true;
+ } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
+ hide = true;
+ this.selfHidden = true;
+ }
+ this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+ for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
+ if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
+ }, this);
+
+ this.internalChain.clearChain();
+ this.internalChain.chain(function(){
+ if (options.resetHeight && !this.selfHidden){
+ var el = elements[index];
+ if (el) el.setStyle('height', 'auto');
+ }
+ }.bind(this));
+
+ return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
+ }
+
+});
+
+
+
+/*
+---
+
+script: Fx.Move.js
+
+name: Fx.Move
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {
+ relativeTo: document.body,
+ position: 'center',
+ edge: false,
+ offset: {x: 0, y: 0}
+ },
+
+ start: function(destination){
+ var element = this.element,
+ topLeft = element.getStyles('top', 'left');
+ if (topLeft.top == 'auto' || topLeft.left == 'auto'){
+ element.setPosition(element.getPosition(element.getOffsetParent()));
+ }
+ return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
+ }
+
+});
+
+Element.Properties.move = {
+
+ set: function(options){
+ this.get('move').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var move = this.retrieve('move');
+ if (!move){
+ move = new Fx.Move(this, {link: 'cancel'});
+ this.store('move', move);
+ }
+ return move;
+ }
+
+};
+
+Element.implement({
+
+ move: function(options){
+ this.get('move').start(options);
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.Scroll.js
+
+name: Fx.Scroll
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+(function(){
+
+Fx.Scroll = new Class({
+
+ Extends: Fx,
+
+ options: {
+ offset: {x: 0, y: 0},
+ wheelStops: true
+ },
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+
+ if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+ if (this.options.wheelStops){
+ var stopper = this.element,
+ cancel = this.cancel.pass(false, this);
+ this.addEvent('start', function(){
+ stopper.addEvent('mousewheel', cancel);
+ }, true);
+ this.addEvent('complete', function(){
+ stopper.removeEvent('mousewheel', cancel);
+ }, true);
+ }
+ },
+
+ set: function(){
+ var now = Array.flatten(arguments);
+ this.element.scrollTo(now[0], now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(x, y){
+ if (!this.check(x, y)) return this;
+ var scroll = this.element.getScroll();
+ return this.parent([scroll.x, scroll.y], [x, y]);
+ },
+
+ calculateScroll: function(x, y){
+ var element = this.element,
+ scrollSize = element.getScrollSize(),
+ scroll = element.getScroll(),
+ size = element.getSize(),
+ offset = this.options.offset,
+ values = {x: x, y: y};
+
+ for (var z in values){
+ if (!values[z] && values[z] !== 0) values[z] = scroll[z];
+ if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
+ values[z] += offset[z];
+ }
+
+ return [values.x, values.y];
+ },
+
+ toTop: function(){
+ return this.start.apply(this, this.calculateScroll(false, 0));
+ },
+
+ toLeft: function(){
+ return this.start.apply(this, this.calculateScroll(0, false));
+ },
+
+ toRight: function(){
+ return this.start.apply(this, this.calculateScroll('right', false));
+ },
+
+ toBottom: function(){
+ return this.start.apply(this, this.calculateScroll(false, 'bottom'));
+ },
+
+ toElement: function(el, axes){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
+ var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
+ return axes.contains(axis) ? value + scroll[axis] : false;
+ });
+ return this.start.apply(this, this.calculateScroll(position.x, position.y));
+ },
+
+ toElementEdge: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize(),
+ edge = {
+ x: position.x + size.x,
+ y: position.y + size.y
+ };
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+ if (position[axis] < scroll[axis]) to[axis] = position[axis];
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ },
+
+ toElementCenter: function(el, axes, offset){
+ axes = axes ? Array.from(axes) : ['x', 'y'];
+ el = document.id(el);
+ var to = {},
+ position = el.getPosition(this.element),
+ size = el.getSize(),
+ scroll = this.element.getScroll(),
+ containerSize = this.element.getSize();
+
+ ['x', 'y'].each(function(axis){
+ if (axes.contains(axis)){
+ to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
+ }
+ if (to[axis] == null) to[axis] = scroll[axis];
+ if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+ }, this);
+
+ if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+ return this;
+ }
+
+});
+
+
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+})();
+
+/*
+---
+
+script: Fx.Slide.js
+
+name: Fx.Slide
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Fx
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+ Extends: Fx,
+
+ options: {
+ mode: 'vertical',
+ wrapper: false,
+ hideOverflow: true,
+ resetHeight: false
+ },
+
+ initialize: function(element, options){
+ element = this.element = this.subject = document.id(element);
+ this.parent(options);
+ options = this.options;
+
+ var wrapper = element.retrieve('wrapper'),
+ styles = element.getStyles('margin', 'position', 'overflow');
+
+ if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
+ if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
+
+ if (!wrapper) wrapper = new Element('div', {
+ styles: styles
+ }).wraps(element);
+
+ element.store('wrapper', wrapper).setStyle('margin', 0);
+ if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
+
+ this.now = [];
+ this.open = true;
+ this.wrapper = wrapper;
+
+ this.addEvent('complete', function(){
+ this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
+ if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
+ }, true);
+ },
+
+ vertical: function(){
+ this.margin = 'margin-top';
+ this.layout = 'height';
+ this.offset = this.element.offsetHeight;
+ },
+
+ horizontal: function(){
+ this.margin = 'margin-left';
+ this.layout = 'width';
+ this.offset = this.element.offsetWidth;
+ },
+
+ set: function(now){
+ this.element.setStyle(this.margin, now[0]);
+ this.wrapper.setStyle(this.layout, now[1]);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ return [0, 1].map(function(i){
+ return Fx.compute(from[i], to[i], delta);
+ });
+ },
+
+ start: function(how, mode){
+ if (!this.check(how, mode)) return this;
+ this[mode || this.options.mode]();
+
+ var margin = this.element.getStyle(this.margin).toInt(),
+ layout = this.wrapper.getStyle(this.layout).toInt(),
+ caseIn = [[margin, layout], [0, this.offset]],
+ caseOut = [[margin, layout], [-this.offset, 0]],
+ start;
+
+ switch (how){
+ case 'in': start = caseIn; break;
+ case 'out': start = caseOut; break;
+ case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+ }
+ return this.parent(start[0], start[1]);
+ },
+
+ slideIn: function(mode){
+ return this.start('in', mode);
+ },
+
+ slideOut: function(mode){
+ return this.start('out', mode);
+ },
+
+ hide: function(mode){
+ this[mode || this.options.mode]();
+ this.open = false;
+ return this.set([-this.offset, 0]);
+ },
+
+ show: function(mode){
+ this[mode || this.options.mode]();
+ this.open = true;
+ return this.set([0, this.offset]);
+ },
+
+ toggle: function(mode){
+ return this.start('toggle', mode);
+ }
+
+});
+
+Element.Properties.slide = {
+
+ set: function(options){
+ this.get('slide').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var slide = this.retrieve('slide');
+ if (!slide){
+ slide = new Fx.Slide(this, {link: 'cancel'});
+ this.store('slide', slide);
+ }
+ return slide;
+ }
+
+};
+
+Element.implement({
+
+ slide: function(how, mode){
+ how = how || 'toggle';
+ var slide = this.get('slide'), toggle;
+ switch (how){
+ case 'hide': slide.hide(mode); break;
+ case 'show': slide.show(mode); break;
+ case 'toggle':
+ var flag = this.retrieve('slide:flag', slide.open);
+ slide[flag ? 'slideOut' : 'slideIn'](mode);
+ this.store('slide:flag', !flag);
+ toggle = true;
+ break;
+ default: slide.start(how, mode);
+ }
+ if (!toggle) this.eliminate('slide:flag');
+ return this;
+ }
+
+});
+
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+name: Fx.SmoothScroll
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Slick.Finder
+ - Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+Fx.SmoothScroll = new Class({
+
+ Extends: Fx.Scroll,
+
+ options: {
+ axes: ['x', 'y']
+ },
+
+ initialize: function(options, context){
+ context = context || document;
+ this.doc = context.getDocument();
+ this.parent(this.doc, options);
+
+ var win = context.getWindow(),
+ location = win.location.href.match(/^[^#]*/)[0] + '#',
+ links = $$(this.options.links || this.doc.links);
+
+ links.each(function(link){
+ if (link.href.indexOf(location) != 0) return;
+ var anchor = link.href.substr(location.length);
+ if (anchor) this.useLink(link, anchor);
+ }, this);
+
+ this.addEvent('complete', function(){
+ win.location.hash = this.anchor;
+ this.element.scrollTo(this.to[0], this.to[1]);
+ }, true);
+ },
+
+ useLink: function(link, anchor){
+
+ link.addEvent('click', function(event){
+ var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+ if (!el) return;
+
+ event.preventDefault();
+ this.toElement(el, this.options.axes).chain(function(){
+ this.fireEvent('scrolledTo', [link, el]);
+ }.bind(this));
+
+ this.anchor = anchor;
+
+ }.bind(this));
+
+ return this;
+ }
+});
+
+/*
+---
+
+script: Fx.Sort.js
+
+name: Fx.Sort
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Dimensions
+ - Fx.Elements
+ - Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+ Extends: Fx.Elements,
+
+ options: {
+ mode: 'vertical'
+ },
+
+ initialize: function(elements, options){
+ this.parent(elements, options);
+ this.elements.each(function(el){
+ if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+ });
+ this.setDefaultOrder();
+ },
+
+ setDefaultOrder: function(){
+ this.currentOrder = this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ sort: function(){
+ if (!this.check(arguments)) return this;
+ var newOrder = Array.flatten(arguments);
+
+ var top = 0,
+ left = 0,
+ next = {},
+ zero = {},
+ vert = this.options.mode == 'vertical';
+
+ var current = this.elements.map(function(el, index){
+ var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+ var val;
+ if (vert){
+ val = {
+ top: top,
+ margin: size['margin-top'],
+ height: size.totalHeight
+ };
+ top += val.height - size['margin-top'];
+ } else {
+ val = {
+ left: left,
+ margin: size['margin-left'],
+ width: size.totalWidth
+ };
+ left += val.width;
+ }
+ var plane = vert ? 'top' : 'left';
+ zero[index] = {};
+ var start = el.getStyle(plane).toInt();
+ zero[index][plane] = start || 0;
+ return val;
+ }, this);
+
+ this.set(zero);
+ newOrder = newOrder.map(function(i){ return i.toInt(); });
+ if (newOrder.length != this.elements.length){
+ this.currentOrder.each(function(index){
+ if (!newOrder.contains(index)) newOrder.push(index);
+ });
+ if (newOrder.length > this.elements.length)
+ newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+ }
+ var margin = 0;
+ top = left = 0;
+ newOrder.each(function(item){
+ var newPos = {};
+ if (vert){
+ newPos.top = top - current[item].top - margin;
+ top += current[item].height;
+ } else {
+ newPos.left = left - current[item].left;
+ left += current[item].width;
+ }
+ margin = margin + current[item].margin;
+ next[item]=newPos;
+ }, this);
+ var mapped = {};
+ Array.clone(newOrder).sort().each(function(index){
+ mapped[index] = next[index];
+ });
+ this.start(mapped);
+ this.currentOrder = newOrder;
+
+ return this;
+ },
+
+ rearrangeDOM: function(newOrder){
+ newOrder = newOrder || this.currentOrder;
+ var parent = this.elements[0].getParent();
+ var rearranged = [];
+ this.elements.setStyle('opacity', 0);
+ //move each element and store the new default order
+ newOrder.each(function(index){
+ rearranged.push(this.elements[index].inject(parent).setStyles({
+ top: 0,
+ left: 0
+ }));
+ }, this);
+ this.elements.setStyle('opacity', 1);
+ this.elements = $$(rearranged);
+ this.setDefaultOrder();
+ return this;
+ },
+
+ getDefaultOrder: function(){
+ return this.elements.map(function(el, index){
+ return index;
+ });
+ },
+
+ getCurrentOrder: function(){
+ return this.currentOrder;
+ },
+
+ forward: function(){
+ return this.sort(this.getDefaultOrder());
+ },
+
+ backward: function(){
+ return this.sort(this.getDefaultOrder().reverse());
+ },
+
+ reverse: function(){
+ return this.sort(this.currentOrder.reverse());
+ },
+
+ sortByElements: function(elements){
+ return this.sort(elements.map(function(el){
+ return this.elements.indexOf(el);
+ }, this));
+ },
+
+ swap: function(one, two){
+ if (typeOf(one) == 'element') one = this.elements.indexOf(one);
+ if (typeOf(two) == 'element') two = this.elements.indexOf(two);
+
+ var newOrder = Array.clone(this.currentOrder);
+ newOrder[this.currentOrder.indexOf(one)] = two;
+ newOrder[this.currentOrder.indexOf(two)] = one;
+
+ return this.sort(newOrder);
+ }
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+name: Keyboard
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Element.Event.Pseudos.Keys
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+
+ var Keyboard = this.Keyboard = new Class({
+
+ Extends: Events,
+
+ Implements: [Options],
+
+ options: {/*
+ onActivate: function(){},
+ onDeactivate: function(){},*/
+ defaultEventType: 'keydown',
+ active: false,
+ manager: null,
+ events: {},
+ nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+ },
+
+ initialize: function(options){
+ if (options && options.manager){
+ this._manager = options.manager;
+ delete options.manager;
+ }
+ this.setOptions(options);
+ this._setup();
+ },
+
+ addEvent: function(type, fn, internal){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+ },
+
+ removeEvent: function(type, fn){
+ return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+ },
+
+ toggleActive: function(){
+ return this[this.isActive() ? 'deactivate' : 'activate']();
+ },
+
+ activate: function(instance){
+ if (instance){
+ if (instance.isActive()) return this;
+ //if we're stealing focus, store the last keyboard to have it so the relinquish command works
+ if (this._activeKB && instance != this._activeKB){
+ this.previous = this._activeKB;
+ this.previous.fireEvent('deactivate');
+ }
+ //if we're enabling a child, assign it so that events are now passed to it
+ this._activeKB = instance.fireEvent('activate');
+ Keyboard.manager.fireEvent('changed');
+ } else if (this._manager){
+ //else we're enabling ourselves, we must ask our parent to do it for us
+ this._manager.activate(this);
+ }
+ return this;
+ },
+
+ isActive: function(){
+ return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
+ },
+
+ deactivate: function(instance){
+ if (instance){
+ if (instance === this._activeKB){
+ this._activeKB = null;
+ instance.fireEvent('deactivate');
+ Keyboard.manager.fireEvent('changed');
+ }
+ } else if (this._manager){
+ this._manager.deactivate(this);
+ }
+ return this;
+ },
+
+ relinquish: function(){
+ if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
+ else this.deactivate();
+ return this;
+ },
+
+ //management logic
+ manage: function(instance){
+ if (instance._manager) instance._manager.drop(instance);
+ this._instances.push(instance);
+ instance._manager = this;
+ if (!this._activeKB) this.activate(instance);
+ return this;
+ },
+
+ drop: function(instance){
+ instance.relinquish();
+ this._instances.erase(instance);
+ if (this._activeKB == instance){
+ if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
+ else this._activeKB = this._instances[0];
+ }
+ return this;
+ },
+
+ trace: function(){
+ Keyboard.trace(this);
+ },
+
+ each: function(fn){
+ Keyboard.each(this, fn);
+ },
+
+ /*
+ PRIVATE METHODS
+ */
+
+ _instances: [],
+
+ _disable: function(instance){
+ if (this._activeKB == instance) this._activeKB = null;
+ },
+
+ _setup: function(){
+ this.addEvents(this.options.events);
+ //if this is the root manager, nothing manages it
+ if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
+ if (this.options.active) this.activate();
+ else this.relinquish();
+ },
+
+ _handle: function(event, type){
+ //Keyboard.stop(event) prevents key propagation
+ if (event.preventKeyboardPropagation) return;
+
+ var bubbles = !!this._manager;
+ if (bubbles && this._activeKB){
+ this._activeKB._handle(event, type);
+ if (event.preventKeyboardPropagation) return;
+ }
+ this.fireEvent(type, event);
+
+ if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
+ }
+
+ });
+
+ var parsed = {};
+ var modifiers = ['shift', 'control', 'alt', 'meta'];
+ var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+
+ Keyboard.parse = function(type, eventType, ignore){
+ if (ignore && ignore.contains(type.toLowerCase())) return type;
+
+ type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+ eventType = $1;
+ return '';
+ });
+
+ if (!parsed[type]){
+ if (type != '+'){
+ var key, mods = {};
+ type.split('+').each(function(part){
+ if (regex.test(part)) mods[part] = true;
+ else key = part;
+ });
+
+ mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+
+ var keys = [];
+ modifiers.each(function(mod){
+ if (mods[mod]) keys.push(mod);
+ });
+
+ if (key) keys.push(key);
+ parsed[type] = keys.join('+');
+ } else {
+ parsed[type] = type;
+ }
+ }
+
+ return eventType + ':keys(' + parsed[type] + ')';
+ };
+
+ Keyboard.each = function(keyboard, fn){
+ var current = keyboard || Keyboard.manager;
+ while (current){
+ fn(current);
+ current = current._activeKB;
+ }
+ };
+
+ Keyboard.stop = function(event){
+ event.preventKeyboardPropagation = true;
+ };
+
+ Keyboard.manager = new Keyboard({
+ active: true
+ });
+
+ Keyboard.trace = function(keyboard){
+ keyboard = keyboard || Keyboard.manager;
+ var hasConsole = window.console && console.log;
+ if (hasConsole) console.log('the following items have focus: ');
+ Keyboard.each(keyboard, function(current){
+ if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
+ });
+ };
+
+ var handler = function(event){
+ var keys = [];
+ modifiers.each(function(mod){
+ if (event[mod]) keys.push(mod);
+ });
+
+ if (!regex.test(event.key)) keys.push(event.key);
+ Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
+ };
+
+ document.addEvents({
+ 'keyup': handler,
+ 'keydown': handler
+ });
+
+})();
+
+/*
+---
+
+script: Keyboard.Extras.js
+
+name: Keyboard.Extras
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - Keyboard
+ - MooTools.More
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+ /*
+ shortcut should be in the format of:
+ {
+ 'keys': 'shift+s', // the default to add as an event.
+ 'description': 'blah blah blah', // a brief description of the functionality.
+ 'handler': function(){} // the event handler to run when keys are pressed.
+ }
+ */
+ addShortcut: function(name, shortcut){
+ this._shortcuts = this._shortcuts || [];
+ this._shortcutIndex = this._shortcutIndex || {};
+
+ shortcut.getKeyboard = Function.from(this);
+ shortcut.name = name;
+ this._shortcutIndex[name] = shortcut;
+ this._shortcuts.push(shortcut);
+ if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+ return this;
+ },
+
+ addShortcuts: function(obj){
+ for (var name in obj) this.addShortcut(name, obj[name]);
+ return this;
+ },
+
+ removeShortcut: function(name){
+ var shortcut = this.getShortcut(name);
+ if (shortcut && shortcut.keys){
+ this.removeEvent(shortcut.keys, shortcut.handler);
+ delete this._shortcutIndex[name];
+ this._shortcuts.erase(shortcut);
+ }
+ return this;
+ },
+
+ removeShortcuts: function(names){
+ names.each(this.removeShortcut, this);
+ return this;
+ },
+
+ getShortcuts: function(){
+ return this._shortcuts || [];
+ },
+
+ getShortcut: function(name){
+ return (this._shortcutIndex || {})[name];
+ }
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+ Array.from(shortcuts).each(function(shortcut){
+ shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+ shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+ shortcut.keys = newKeys;
+ shortcut.getKeyboard().fireEvent('rebound');
+ });
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard){
+ var activeKBS = [], activeSCS = [];
+ Keyboard.each(keyboard, [].push.bind(activeKBS));
+ activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+ return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+ opts = opts || {};
+ var shortcuts = opts.many ? [] : null,
+ set = opts.many ? function(kb){
+ var shortcut = kb.getShortcut(name);
+ if (shortcut) shortcuts.push(shortcut);
+ } : function(kb){
+ if (!shortcuts) shortcuts = kb.getShortcut(name);
+ };
+ Keyboard.each(keyboard, set);
+ return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard){
+ return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+
+/*
+---
+
+script: HtmlTable.js
+
+name: HtmlTable
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ properties: {
+ cellpadding: 0,
+ cellspacing: 0,
+ border: 0
+ },
+ rows: [],
+ headers: [],
+ footers: []
+ },
+
+ property: 'HtmlTable',
+
+ initialize: function(){
+ var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
+ this.setOptions(params.options);
+ if (!params.table && params.id) params.table = document.id(params.id);
+ this.element = params.table || new Element('table', this.options.properties);
+ if (this.occlude()) return this.occluded;
+ this.build();
+ },
+
+ build: function(){
+ this.element.store('HtmlTable', this);
+
+ this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+ $$(this.body.rows);
+
+ if (this.options.headers.length) this.setHeaders(this.options.headers);
+ else this.thead = document.id(this.element.tHead);
+
+ if (this.thead) this.head = this.getHead();
+ if (this.options.footers.length) this.setFooters(this.options.footers);
+
+ this.tfoot = document.id(this.element.tFoot);
+ if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
+
+ this.options.rows.each(function(row){
+ this.push(row);
+ }, this);
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ empty: function(){
+ this.body.empty();
+ return this;
+ },
+
+ set: function(what, items){
+ var target = (what == 'headers') ? 'tHead' : 'tFoot',
+ lower = target.toLowerCase();
+
+ this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
+ var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
+
+ if (what == 'headers') this.head = this.getHead();
+ else this.foot = this.getHead();
+
+ return data;
+ },
+
+ getHead: function(){
+ var rows = this.thead.rows;
+ return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
+ },
+
+ setHeaders: function(headers){
+ this.set('headers', headers);
+ return this;
+ },
+
+ setFooters: function(footers){
+ this.set('footers', footers);
+ return this;
+ },
+
+ update: function(tr, row, tag){
+ var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
+
+ row.each(function(data, index){
+ var td = tds[index] || new Element(tag || 'td').inject(tr),
+ content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data,
+ type = typeOf(content);
+
+ if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties);
+ if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
+ else td.set('html', content);
+
+ if (index > last) tds.push(td);
+ else tds[index] = td;
+ });
+
+ return {
+ tr: tr,
+ tds: tds
+ };
+ },
+
+ push: function(row, rowProperties, target, tag, where){
+ if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
+ row.inject(target || this.body, where);
+ return {
+ tr: row,
+ tds: row.getChildren('td')
+ };
+ }
+ return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
+ },
+
+ pushMany: function(rows, rowProperties, target, tag, where){
+ return rows.map(function(row){
+ return this.push(row, rowProperties, target, tag, where);
+ }, this);
+ }
+
+});
+
+
+['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+ HtmlTable.implement(method, function(){
+ this.element[method].apply(this.element, arguments);
+ return this;
+ });
+});
+
+
+
+/*
+---
+
+script: HtmlTable.Select.js
+
+name: HtmlTable.Select
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - Keyboard
+ - Keyboard.Extras
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - Element.Shortcuts
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ /*onRowFocus: function(){},
+ onRowUnfocus: function(){},*/
+ useKeyboard: true,
+ classRowSelected: 'table-tr-selected',
+ classRowHovered: 'table-tr-hovered',
+ classSelectable: 'table-selectable',
+ shiftForMultiSelect: true,
+ allowMultiSelect: true,
+ selectable: false,
+ selectHiddenRows: false
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+
+ this.selectedRows = new Elements();
+
+ if (!this.bound) this.bound = {};
+ this.bound.mouseleave = this.mouseleave.bind(this);
+ this.bound.clickRow = this.clickRow.bind(this);
+ this.bound.activateKeyboard = function(){
+ if (this.keyboard && this.selectEnabled) this.keyboard.activate();
+ }.bind(this);
+
+ if (this.options.selectable) this.enableSelect();
+ },
+
+ empty: function(){
+ if (this.body.rows.length) this.selectNone();
+ return this.previous();
+ },
+
+ enableSelect: function(){
+ this.selectEnabled = true;
+ this.attachSelects();
+ this.element.addClass(this.options.classSelectable);
+ return this;
+ },
+
+ disableSelect: function(){
+ this.selectEnabled = false;
+ this.attachSelects(false);
+ this.element.removeClass(this.options.classSelectable);
+ return this;
+ },
+
+ push: function(){
+ var ret = this.previous.apply(this, arguments);
+ this.updateSelects();
+ return ret;
+ },
+
+ toggleRow: function(row){
+ return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
+ },
+
+ selectRow: function(row, _nocheck){
+ //private variable _nocheck: boolean whether or not to confirm the row is in the table body
+ //added here for optimization when selecting ranges
+ if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+ if (!this.options.allowMultiSelect) this.selectNone();
+
+ if (!this.isSelected(row)){
+ this.selectedRows.push(row);
+ row.addClass(this.options.classRowSelected);
+ this.fireEvent('rowFocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ }
+
+ this.focused = row;
+ document.clearSelection();
+
+ return this;
+ },
+
+ isSelected: function(row){
+ return this.selectedRows.contains(row);
+ },
+
+ getSelected: function(){
+ return this.selectedRows;
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.selectable){
+ previousSerialization.selectedRows = this.selectedRows.map(function(row){
+ return Array.indexOf(this.body.rows, row);
+ }.bind(this));
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.selectable && tableState.selectedRows){
+ tableState.selectedRows.each(function(index){
+ this.selectRow(this.body.rows[index]);
+ }.bind(this));
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ deselectRow: function(row, _nocheck){
+ if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
+
+ this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
+ row.removeClass(this.options.classRowSelected);
+ this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+ this.fireEvent('stateChanged');
+ return this;
+ },
+
+ selectAll: function(selectNone){
+ if (!selectNone && !this.options.allowMultiSelect) return;
+ this.selectRange(0, this.body.rows.length, selectNone);
+ return this;
+ },
+
+ selectNone: function(){
+ return this.selectAll(true);
+ },
+
+ selectRange: function(startRow, endRow, _deselect){
+ if (!this.options.allowMultiSelect && !_deselect) return;
+ var method = _deselect ? 'deselectRow' : 'selectRow',
+ rows = Array.clone(this.body.rows);
+
+ if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
+ if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
+ endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
+
+ if (endRow < startRow){
+ var tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+
+ for (var i = startRow; i <= endRow; i++){
+ if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
+ }
+
+ return this;
+ },
+
+ deselectRange: function(startRow, endRow){
+ this.selectRange(startRow, endRow, true);
+ },
+
+/*
+ Private methods:
+*/
+
+ enterRow: function(row){
+ if (this.hovered) this.hovered = this.leaveRow(this.hovered);
+ this.hovered = row.addClass(this.options.classRowHovered);
+ },
+
+ leaveRow: function(row){
+ row.removeClass(this.options.classRowHovered);
+ },
+
+ updateSelects: function(){
+ Array.each(this.body.rows, function(row){
+ var binders = row.retrieve('binders');
+ if (!binders && !this.selectEnabled) return;
+ if (!binders){
+ binders = {
+ mouseenter: this.enterRow.pass([row], this),
+ mouseleave: this.leaveRow.pass([row], this)
+ };
+ row.store('binders', binders);
+ }
+ if (this.selectEnabled) row.addEvents(binders);
+ else row.removeEvents(binders);
+ }, this);
+ },
+
+ shiftFocus: function(offset, event){
+ if (!this.focused) return this.selectRow(this.body.rows[0], event);
+ var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
+ if (to === null || this.focused == this.body.rows[to]) return this;
+ this.toggleRow(this.body.rows[to], event);
+ },
+
+ clickRow: function(event, row){
+ var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
+ if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
+
+ if (event.rightClick) this.selectRow(row);
+ else this.toggleRow(row);
+
+ if (event.shift){
+ this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
+ this.focused = row;
+ }
+ this.rangeStart = row;
+ },
+
+ getRowByOffset: function(offset, includeHiddenRows){
+ if (!this.focused) return 0;
+ var index = Array.indexOf(this.body.rows, this.focused);
+ if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
+ if (includeHiddenRows){
+ index += offset;
+ } else {
+ var limit = 0,
+ count = 0;
+ if (offset > 0){
+ while (count < offset && index < this.body.rows.length -1){
+ if (this.body.rows[++index].isDisplayed()) count++;
+ }
+ } else {
+ while (count > offset && index > 0){
+ if (this.body.rows[--index].isDisplayed()) count--;
+ }
+ }
+ }
+ return index;
+ },
+
+ attachSelects: function(attach){
+ attach = attach != null ? attach : true;
+
+ var method = attach ? 'addEvents' : 'removeEvents';
+ this.element[method]({
+ mouseleave: this.bound.mouseleave,
+ click: this.bound.activateKeyboard
+ });
+
+ this.body[method]({
+ 'click:relay(tr)': this.bound.clickRow,
+ 'contextmenu:relay(tr)': this.bound.clickRow
+ });
+
+ if (this.options.useKeyboard || this.keyboard){
+ if (!this.keyboard) this.keyboard = new Keyboard();
+ if (!this.selectKeysDefined){
+ this.selectKeysDefined = true;
+ var timer, held;
+
+ var move = function(offset){
+ var mover = function(e){
+ clearTimeout(timer);
+ e.preventDefault();
+ var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
+ if (e.shift && to && this.isSelected(to)){
+ this.deselectRow(this.focused);
+ this.focused = to;
+ } else {
+ if (to && (!this.options.allowMultiSelect || !e.shift)){
+ this.selectNone();
+ }
+ this.shiftFocus(offset, e);
+ }
+
+ if (held){
+ timer = mover.delay(100, this, e);
+ } else {
+ timer = (function(){
+ held = true;
+ mover(e);
+ }).delay(400);
+ }
+ }.bind(this);
+ return mover;
+ }.bind(this);
+
+ var clear = function(){
+ clearTimeout(timer);
+ held = false;
+ };
+
+ this.keyboard.addEvents({
+ 'keydown:shift+up': move(-1),
+ 'keydown:shift+down': move(1),
+ 'keyup:shift+up': clear,
+ 'keyup:shift+down': clear,
+ 'keyup:up': clear,
+ 'keyup:down': clear
+ });
+
+ var shiftHint = '';
+ if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
+ shiftHint = " (Shift multi-selects).";
+ }
+
+ this.keyboard.addShortcuts({
+ 'Select Previous Row': {
+ keys: 'up',
+ shortcut: 'up arrow',
+ handler: move(-1),
+ description: 'Select the previous row in the table.' + shiftHint
+ },
+ 'Select Next Row': {
+ keys: 'down',
+ shortcut: 'down arrow',
+ handler: move(1),
+ description: 'Select the next row in the table.' + shiftHint
+ }
+ });
+
+ }
+ this.keyboard[attach ? 'activate' : 'deactivate']();
+ }
+ this.updateSelects();
+ },
+
+ mouseleave: function(){
+ if (this.hovered) this.leaveRow(this.hovered);
+ }
+
+});
+
+/*
+---
+
+script: HtmlTable.Sort.js
+
+name: HtmlTable.Sort
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Hash
+ - HtmlTable
+ - Class.refactor
+ - Element.Delegation.Pseudo
+ - String.Extras
+ - Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+(function(){
+
+var readOnlyNess = document.createElement('table');
+try {
+ readOnlyNess.innerHTML = '<tr><td></td></tr>';
+ readOnlyNess = readOnlyNess.childNodes.length === 0;
+} catch (e){
+ readOnlyNess = true;
+}
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {/*
+ onSort: function(){}, */
+ sortIndex: 0,
+ sortReverse: false,
+ parsers: [],
+ defaultParser: 'string',
+ classSortable: 'table-sortable',
+ classHeadSort: 'table-th-sort',
+ classHeadSortRev: 'table-th-sort-rev',
+ classNoSort: 'table-th-nosort',
+ classGroupHead: 'table-tr-group-head',
+ classGroup: 'table-tr-group',
+ classCellSort: 'table-td-sort',
+ classSortSpan: 'table-th-sort-span',
+ sortable: false,
+ thSelector: 'th'
+ },
+
+ initialize: function (){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ this.sorted = {index: null, dir: 1};
+ if (!this.bound) this.bound = {};
+ this.bound.headClick = this.headClick.bind(this);
+ this.sortSpans = new Elements();
+ if (this.options.sortable){
+ this.enableSort();
+ if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+ }
+ },
+
+ attachSorts: function(attach){
+ this.detachSorts();
+ if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
+ },
+
+ detachSorts: function(){
+ this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
+ },
+
+ setHeaders: function(){
+ this.previous.apply(this, arguments);
+ if (this.sortable) this.setParsers();
+ },
+
+ setParsers: function(){
+ this.parsers = this.detectParsers();
+ },
+
+ detectParsers: function(){
+ return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
+ },
+
+ detectParser: function(cell, index){
+ if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
+ var thDiv = new Element('div');
+ thDiv.adopt(cell.childNodes).inject(cell);
+ var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
+ this.sortSpans.push(sortSpan);
+ var parser = this.options.parsers[index],
+ rows = this.body.rows,
+ cancel;
+ switch (typeOf(parser)){
+ case 'function': parser = {convert: parser}; cancel = true; break;
+ case 'string': parser = parser; cancel = true; break;
+ }
+ if (!cancel){
+ HtmlTable.ParserPriority.some(function(parserName){
+ var current = HtmlTable.Parsers[parserName],
+ match = current.match;
+ if (!match) return false;
+ for (var i = 0, j = rows.length; i < j; i++){
+ var cell = document.id(rows[i].cells[index]),
+ text = cell ? cell.get('html').clean() : '';
+ if (text && match.test(text)){
+ parser = current;
+ return true;
+ }
+ }
+ });
+ }
+ if (!parser) parser = this.options.defaultParser;
+ cell.store('htmltable-parser', parser);
+ return parser;
+ },
+
+ headClick: function(event, el){
+ if (!this.head || el.hasClass(this.options.classNoSort)) return;
+ return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
+ },
+
+ serialize: function(){
+ var previousSerialization = this.previous.apply(this, arguments) || {};
+ if (this.options.sortable){
+ previousSerialization.sortIndex = this.sorted.index;
+ previousSerialization.sortReverse = this.sorted.reverse;
+ }
+ return previousSerialization;
+ },
+
+ restore: function(tableState){
+ if(this.options.sortable && tableState.sortIndex){
+ this.sort(tableState.sortIndex, tableState.sortReverse);
+ }
+ this.previous.apply(this, arguments);
+ },
+
+ setSortedState: function(index, reverse){
+ if (reverse != null) this.sorted.reverse = reverse;
+ else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
+ else this.sorted.reverse = this.sorted.index == null;
+
+ if (index != null) this.sorted.index = index;
+ },
+
+ setHeadSort: function(sorted){
+ var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
+ return row.getElements(this.options.thSelector)[this.sorted.index];
+ }, this).clean());
+ if (!head.length) return;
+ if (sorted){
+ head.addClass(this.options.classHeadSort);
+ if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+ else head.removeClass(this.options.classHeadSortRev);
+ } else {
+ head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+ }
+ },
+
+ setRowSort: function(data, pre){
+ var count = data.length,
+ body = this.body,
+ group,
+ rowIndex;
+
+ while (count){
+ var item = data[--count],
+ position = item.position,
+ row = body.rows[position];
+
+ if (row.disabled) continue;
+ if (!pre){
+ group = this.setGroupSort(group, row, item);
+ this.setRowStyle(row, count);
+ }
+ body.appendChild(row);
+
+ for (rowIndex = 0; rowIndex < count; rowIndex++){
+ if (data[rowIndex].position > position) data[rowIndex].position--;
+ }
+ }
+ },
+
+ setRowStyle: function(row, i){
+ this.previous(row, i);
+ row.cells[this.sorted.index].addClass(this.options.classCellSort);
+ },
+
+ setGroupSort: function(group, row, item){
+ if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
+ else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
+ return item.value;
+ },
+
+ getParser: function(){
+ var parser = this.parsers[this.sorted.index];
+ return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
+ },
+
+ sort: function(index, reverse, pre, sortFunction){
+ if (!this.head) return;
+
+ if (!pre){
+ this.clearSort();
+ this.setSortedState(index, reverse);
+ this.setHeadSort(true);
+ }
+
+ var parser = this.getParser();
+ if (!parser) return;
+
+ var rel;
+ if (!readOnlyNess){
+ rel = this.body.getParent();
+ this.body.dispose();
+ }
+
+ var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){
+ if (a.value === b.value) return 0;
+ return a.value > b.value ? 1 : -1;
+ });
+
+ if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
+ this.setRowSort(data, pre);
+
+ if (rel) rel.grab(this.body);
+ this.fireEvent('stateChanged');
+ return this.fireEvent('sort', [this.body, this.sorted.index]);
+ },
+
+ parseData: function(parser){
+ return Array.map(this.body.rows, function(row, i){
+ var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
+ return {
+ position: i,
+ value: value
+ };
+ }, this);
+ },
+
+ clearSort: function(){
+ this.setHeadSort(false);
+ this.body.getElements('td').removeClass(this.options.classCellSort);
+ },
+
+ reSort: function(){
+ if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+ return this;
+ },
+
+ enableSort: function(){
+ this.element.addClass(this.options.classSortable);
+ this.attachSorts(true);
+ this.setParsers();
+ this.sortable = true;
+ return this;
+ },
+
+ disableSort: function(){
+ this.element.removeClass(this.options.classSortable);
+ this.attachSorts(false);
+ this.sortSpans.each(function(span){
+ span.destroy();
+ });
+ this.sortSpans.empty();
+ this.sortable = false;
+ return this;
+ }
+
+});
+
+HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
+
+HtmlTable.Parsers = {
+
+ 'date': {
+ match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+ convert: function(){
+ var d = Date.parse(this.get('text').stripTags());
+ return (typeOf(d) == 'date') ? d.format('db') : '';
+ },
+ type: 'date'
+ },
+ 'input-checked': {
+ match: / type="(radio|checkbox)"/,
+ convert: function(){
+ return this.getElement('input').checked;
+ }
+ },
+ 'input-value': {
+ match: /<input/,
+ convert: function(){
+ return this.getElement('input').value;
+ }
+ },
+ 'number': {
+ match: /^\d+[^\d.,]*$/,
+ convert: function(){
+ return this.get('text').stripTags().toInt();
+ },
+ number: true
+ },
+ 'numberLax': {
+ match: /^[^\d]+\d+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
+ },
+ number: true
+ },
+ 'float': {
+ match: /^[\d]+\.[\d]+/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'floatLax': {
+ match: /^[^\d]+[\d]+\.[\d]+$/,
+ convert: function(){
+ return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
+ },
+ number: true
+ },
+ 'string': {
+ match: null,
+ convert: function(){
+ return this.get('text').stripTags().toLowerCase();
+ }
+ },
+ 'title': {
+ match: null,
+ convert: function(){
+ return this.title;
+ }
+ }
+
+};
+
+
+
+HtmlTable.defineParsers = function(parsers){
+ HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
+ for (var parser in parsers){
+ HtmlTable.ParserPriority.unshift(parser);
+ }
+};
+
+})();
+
+
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+name: HtmlTable.Zebra
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - HtmlTable
+ - Element.Shortcuts
+ - Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+ options: {
+ classZebra: 'table-tr-odd',
+ zebra: true,
+ zebraOnlyVisibleRows: true
+ },
+
+ initialize: function(){
+ this.previous.apply(this, arguments);
+ if (this.occluded) return this.occluded;
+ if (this.options.zebra) this.updateZebras();
+ },
+
+ updateZebras: function(){
+ var index = 0;
+ Array.each(this.body.rows, function(row){
+ if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
+ this.zebra(row, index++);
+ }
+ }, this);
+ },
+
+ setRowStyle: function(row, i){
+ if (this.previous) this.previous(row, i);
+ this.zebra(row, i);
+ },
+
+ zebra: function(row, i){
+ return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+ },
+
+ push: function(){
+ var pushed = this.previous.apply(this, arguments);
+ if (this.options.zebra) this.updateZebras();
+ return pushed;
+ }
+
+});
+
+/*
+---
+
+script: Scroller.js
+
+name: Scroller
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+ Implements: [Events, Options],
+
+ options: {
+ area: 20,
+ velocity: 1,
+ onChange: function(x, y){
+ this.element.scrollTo(x, y);
+ },
+ fps: 50
+ },
+
+ initialize: function(element, options){
+ this.setOptions(options);
+ this.element = document.id(element);
+ this.docBody = document.id(this.element.getDocument().body);
+ this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
+ this.timer = null;
+ this.bound = {
+ attach: this.attach.bind(this),
+ detach: this.detach.bind(this),
+ getCoords: this.getCoords.bind(this)
+ };
+ },
+
+ start: function(){
+ this.listener.addEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ return this;
+ },
+
+ stop: function(){
+ this.listener.removeEvents({
+ mouseover: this.bound.attach,
+ mouseleave: this.bound.detach
+ });
+ this.detach();
+ this.timer = clearInterval(this.timer);
+ return this;
+ },
+
+ attach: function(){
+ this.listener.addEvent('mousemove', this.bound.getCoords);
+ },
+
+ detach: function(){
+ this.listener.removeEvent('mousemove', this.bound.getCoords);
+ this.timer = clearInterval(this.timer);
+ },
+
+ getCoords: function(event){
+ this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+ if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+ },
+
+ scroll: function(){
+ var size = this.element.getSize(),
+ scroll = this.element.getScroll(),
+ pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0},
+ scrollSize = this.element.getScrollSize(),
+ change = {x: 0, y: 0},
+ top = this.options.area.top || this.options.area,
+ bottom = this.options.area.bottom || this.options.area;
+ for (var z in this.page){
+ if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
+ change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
+ } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
+ change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
+ }
+ change[z] = change[z].round();
+ }
+ if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+ }
+
+});
+
+/*
+---
+
+script: Tips.js
+
+name: Tips
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+ - Luis Merino
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+ return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ id: null,
+ onAttach: function(element){},
+ onDetach: function(element){},
+ onBound: function(coords){},*/
+ onShow: function(){
+ this.tip.setStyle('display', 'block');
+ },
+ onHide: function(){
+ this.tip.setStyle('display', 'none');
+ },
+ title: 'title',
+ text: function(element){
+ return element.get('rel') || element.get('href');
+ },
+ showDelay: 100,
+ hideDelay: 100,
+ className: 'tip-wrap',
+ offset: {x: 16, y: 16},
+ windowPadding: {x:0, y:0},
+ fixed: false,
+ waiAria: true
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ options: Type.isObject,
+ elements: function(obj){
+ return obj != null;
+ }
+ });
+ this.setOptions(params.options);
+ if (params.elements) this.attach(params.elements);
+ this.container = new Element('div', {'class': 'tip'});
+
+ if (this.options.id){
+ this.container.set('id', this.options.id);
+ if (this.options.waiAria) this.attachWaiAria();
+ }
+ },
+
+ toElement: function(){
+ if (this.tip) return this.tip;
+
+ this.tip = new Element('div', {
+ 'class': this.options.className,
+ styles: {
+ position: 'absolute',
+ top: 0,
+ left: 0
+ }
+ }).adopt(
+ new Element('div', {'class': 'tip-top'}),
+ this.container,
+ new Element('div', {'class': 'tip-bottom'})
+ );
+
+ return this.tip;
+ },
+
+ attachWaiAria: function(){
+ var id = this.options.id;
+ this.container.set('role', 'tooltip');
+
+ if (!this.waiAria){
+ this.waiAria = {
+ show: function(element){
+ if (id) element.set('aria-describedby', id);
+ this.container.set('aria-hidden', 'false');
+ },
+ hide: function(element){
+ if (id) element.erase('aria-describedby');
+ this.container.set('aria-hidden', 'true');
+ }
+ };
+ }
+ this.addEvents(this.waiAria);
+ },
+
+ detachWaiAria: function(){
+ if (this.waiAria){
+ this.container.erase('role');
+ this.container.erase('aria-hidden');
+ this.removeEvents(this.waiAria);
+ }
+ },
+
+ attach: function(elements){
+ $$(elements).each(function(element){
+ var title = read(this.options.title, element),
+ text = read(this.options.text, element);
+
+ element.set('title', '').store('tip:native', title).retrieve('tip:title', title);
+ element.retrieve('tip:text', text);
+ this.fireEvent('attach', [element]);
+
+ var events = ['enter', 'leave'];
+ if (!this.options.fixed) events.push('move');
+
+ events.each(function(value){
+ var event = element.retrieve('tip:' + value);
+ if (!event) event = function(event){
+ this['element' + value.capitalize()].apply(this, [event, element]);
+ }.bind(this);
+
+ element.store('tip:' + value, event).addEvent('mouse' + value, event);
+ }, this);
+ }, this);
+
+ return this;
+ },
+
+ detach: function(elements){
+ $$(elements).each(function(element){
+ ['enter', 'leave', 'move'].each(function(value){
+ element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+ });
+
+ this.fireEvent('detach', [element]);
+
+ if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+ var original = element.retrieve('tip:native');
+ if (original) element.set('title', original);
+ }
+ }, this);
+
+ return this;
+ },
+
+ elementEnter: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = (function(){
+ this.container.empty();
+
+ ['title', 'text'].each(function(value){
+ var content = element.retrieve('tip:' + value);
+ var div = this['_' + value + 'Element'] = new Element('div', {
+ 'class': 'tip-' + value
+ }).inject(this.container);
+ if (content) this.fill(div, content);
+ }, this);
+ this.show(element);
+ this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+ }).delay(this.options.showDelay, this);
+ },
+
+ elementLeave: function(event, element){
+ clearTimeout(this.timer);
+ this.timer = this.hide.delay(this.options.hideDelay, this, element);
+ this.fireForParent(event, element);
+ },
+
+ setTitle: function(title){
+ if (this._titleElement){
+ this._titleElement.empty();
+ this.fill(this._titleElement, title);
+ }
+ return this;
+ },
+
+ setText: function(text){
+ if (this._textElement){
+ this._textElement.empty();
+ this.fill(this._textElement, text);
+ }
+ return this;
+ },
+
+ fireForParent: function(event, element){
+ element = element.getParent();
+ if (!element || element == document.body) return;
+ if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+ else this.fireForParent(event, element);
+ },
+
+ elementMove: function(event, element){
+ this.position(event);
+ },
+
+ position: function(event){
+ if (!this.tip) document.id(this);
+
+ var size = window.getSize(), scroll = window.getScroll(),
+ tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+ props = {x: 'left', y: 'top'},
+ bounds = {y: false, x2: false, y2: false, x: false},
+ obj = {};
+
+ for (var z in props){
+ obj[props[z]] = event.page[z] + this.options.offset[z];
+ if (obj[props[z]] < 0) bounds[z] = true;
+ if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){
+ obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+ bounds[z+'2'] = true;
+ }
+ }
+
+ this.fireEvent('bound', bounds);
+ this.tip.setStyles(obj);
+ },
+
+ fill: function(element, contents){
+ if (typeof contents == 'string') element.set('html', contents);
+ else element.adopt(contents);
+ },
+
+ show: function(element){
+ if (!this.tip) document.id(this);
+ if (!this.tip.getParent()) this.tip.inject(document.body);
+ this.fireEvent('show', [this.tip, element]);
+ },
+
+ hide: function(element){
+ if (!this.tip) document.id(this);
+ this.fireEvent('hide', [this.tip, element]);
+ }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.EU.Number
+
+description: Number messages for Europe.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.EU.Number]
+
+...
+*/
+
+Locale.define('EU', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: '€ '
+ }
+
+});
+
+/*
+---
+
+script: Locale.Set.From.js
+
+name: Locale.Set.From
+
+description: Provides an alternative way to create Locale.Set objects.
+
+license: MIT-style license
+
+authors:
+ - Tim Wienk
+
+requires:
+ - Core/JSON
+ - Locale
+
+provides: Locale.Set.From
+
+...
+*/
+
+(function(){
+
+var parsers = {
+ 'json': JSON.decode
+};
+
+Locale.Set.defineParser = function(name, fn){
+ parsers[name] = fn;
+};
+
+Locale.Set.from = function(set, type){
+ if (instanceOf(set, Locale.Set)) return set;
+
+ if (!type && typeOf(set) == 'string') type = 'json';
+ if (parsers[type]) set = parsers[type](set);
+
+ var locale = new Locale.Set;
+
+ locale.sets = set.sets || {};
+
+ if (set.inherits){
+ locale.inherits.locales = Array.from(set.inherits.locales);
+ locale.inherits.sets = set.inherits.sets || {};
+ }
+
+ return locale;
+};
+
+})();
+
+/*
+---
+
+name: Locale.ZA.Number
+
+description: Number messages for ZA.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.ZA.Number]
+
+...
+*/
+
+Locale.define('ZA', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ prefix: 'R '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.af-ZA.Date
+
+description: Date messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Date]
+
+...
+*/
+
+Locale.define('af-ZA', 'Date', {
+
+ months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'],
+ days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'VM',
+ PM: 'NM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste';
+ },
+
+ lessThanMinuteAgo: 'minder as \'n minuut gelede',
+ minuteAgo: 'ongeveer \'n minuut gelede',
+ minutesAgo: '{delta} minute gelede',
+ hourAgo: 'omtret \'n uur gelede',
+ hoursAgo: 'ongeveer {delta} ure gelede',
+ dayAgo: '1 dag gelede',
+ daysAgo: '{delta} dae gelede',
+ weekAgo: '1 week gelede',
+ weeksAgo: '{delta} weke gelede',
+ monthAgo: '1 maand gelede',
+ monthsAgo: '{delta} maande gelede',
+ yearAgo: '1 jaar gelede',
+ yearsAgo: '{delta} jare gelede',
+
+ lessThanMinuteUntil: 'oor minder as \'n minuut',
+ minuteUntil: 'oor ongeveer \'n minuut',
+ minutesUntil: 'oor {delta} minute',
+ hourUntil: 'oor ongeveer \'n uur',
+ hoursUntil: 'oor {delta} uur',
+ dayUntil: 'oor ongeveer \'n dag',
+ daysUntil: 'oor {delta} dae',
+ weekUntil: 'oor \'n week',
+ weeksUntil: 'oor {delta} weke',
+ monthUntil: 'oor \'n maand',
+ monthsUntil: 'oor {delta} maande',
+ yearUntil: 'oor \'n jaar',
+ yearsUntil: 'oor {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Form.Validator
+
+description: Form Validator messages for Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+
+provides: [Locale.af-ZA.Form.Validator]
+
+...
+*/
+
+Locale.define('af-ZA', 'FormValidator', {
+
+ required: 'Hierdie veld word vereis.',
+ length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)',
+ minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).',
+ maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).',
+ integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.',
+ numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).',
+ alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.',
+ dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in',
+ dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")',
+ email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".',
+ url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.',
+ currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .',
+ oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.',
+ errorPrefix: 'Fout: ',
+ warningPrefix: 'Waarskuwing: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.',
+ reqChkByNode: 'Geen items is gekies nie.',
+ requiredChk: 'Hierdie veld word vereis.',
+ reqChkByName: 'Kies asseblief \'n {label}.',
+ match: 'Hierdie veld moet by die {matchName} veld pas',
+ startDate: 'die begin datum',
+ endDate: 'die eind datum',
+ currentDate: 'die huidige datum',
+ afterDate: 'Die datum moet dieselfde of na {label} wees.',
+ beforeDate: 'Die datum moet dieselfde of voor {label} wees.',
+ startMonth: 'Kies asseblief \'n begin maand',
+ sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.',
+ creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.'
+
+});
+
+/*
+---
+
+name: Locale.af-ZA.Number
+
+description: Number messages for ZA Afrikaans.
+
+license: MIT-style license
+
+authors:
+ - Werner Mollentze
+
+requires:
+ - Locale
+ - Locale.ZA.Number
+
+provides: [Locale.af-ZA.Number]
+
+...
+*/
+
+Locale.define('af-ZA').inherit('ZA', 'Number');
+
+/*
+---
+
+name: Locale.ar.Date
+
+description: Date messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Date]
+
+...
+*/
+
+Locale.define('ar', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+});
+
+/*
+---
+
+name: Locale.ar.Form.Validator
+
+description: Form Validator messages for Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - Locale
+
+provides: [Locale.ar.Form.Validator]
+
+...
+*/
+
+Locale.define('ar', 'FormValidator', {
+
+ required: 'هذا الحقل مطلوؚ.',
+ minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).',
+ maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).',
+ integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر ع؎ري أو م؊وي (مثال 1.25 ) غير مسموح.',
+ numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+ digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو ؎حطة)',
+ alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.',
+ dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+ dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+ email: 'الرجاء إدخال ؚريد إلكتروني صحيح.',
+ url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com',
+ currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$',
+ oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.',
+ errorPrefix: 'خطأ: ',
+ warningPrefix: 'تحذير: '
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Date
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Date]
+
+...
+*/
+
+Locale.define('ca-CA', 'Date', {
+
+ months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+ months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'],
+ days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+ days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'fa menys d`un minut',
+ minuteAgo: 'fa un minut',
+ minutesAgo: 'fa {delta} minuts',
+ hourAgo: 'fa un hora',
+ hoursAgo: 'fa unes {delta} hores',
+ dayAgo: 'fa un dia',
+ daysAgo: 'fa {delta} dies',
+
+ lessThanMinuteUntil: 'menys d`un minut des d`ara',
+ minuteUntil: 'un minut des d`ara',
+ minutesUntil: '{delta} minuts des d`ara',
+ hourUntil: 'un hora des d`ara',
+ hoursUntil: 'unes {delta} hores des d`ara',
+ dayUntil: '1 dia des d`ara',
+ daysUntil: '{delta} dies des d`ara'
+
+});
+
+/*
+---
+
+name: Locale.ca-CA.Form.Validator
+
+description: Form Validator messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.ca-CA.Form.Validator]
+
+...
+*/
+
+Locale.define('ca-CA', 'FormValidator', {
+
+ required: 'Aquest camp es obligatori.',
+ minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+ maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+ integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+ numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+ alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.',
+ dateSuchAs: 'Per favor introdueix una data valida com {date}',
+ dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".',
+ url: 'Per favor introdueix una URL valida com http://www.example.com.',
+ currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+ oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Avis: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No poden haver espais en aquesta entrada.',
+ reqChkByNode: 'No hi han elements seleccionats.',
+ requiredChk: 'Aquest camp es obligatori.',
+ reqChkByName: 'Per favor selecciona una {label}.',
+ match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+ startDate: 'la data de inici',
+ endDate: 'la data de fi',
+ currentDate: 'la data actual',
+ afterDate: 'La data deu ser igual o posterior a {label}.',
+ beforeDate: 'La data deu ser igual o anterior a {label}.',
+ startMonth: 'Per favor selecciona un mes dÂŽorige',
+ sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});
+
+/*
+---
+
+name: Locale.cs-CZ.Date
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+ - Christopher Zukowski
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Date]
+
+...
+*/
+(function(){
+
+// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('cs-CZ', 'Date', {
+
+ months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+ months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'],
+ days: ['Neděle', 'Pondělí', 'ÚterÜ', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'odp.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'před chvílí',
+ minuteAgo: 'přibliÅŸně před minutou',
+ minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); },
+ hourAgo: 'přibliÅŸně před hodinou',
+ hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'před dnem',
+ daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); },
+ weekAgo: 'před tÜdnem',
+ weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); },
+ monthAgo: 'před měsícem',
+ monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); },
+ yearAgo: 'před rokem',
+ yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); },
+
+ lessThanMinuteUntil: 'za chvíli',
+ minuteUntil: 'přibliÅŸně za minutu',
+ minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); },
+ hourUntil: 'přibliÅŸně za hodinu',
+ hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); },
+ dayUntil: 'za den',
+ daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); },
+ weekUntil: 'za tÜden',
+ weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); },
+ monthUntil: 'za měsíc',
+ monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); },
+ yearUntil: 'za rok',
+ yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.cs-CZ.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan ČernÜ chemiX
+
+requires:
+ - Locale
+
+provides: [Locale.cs-CZ.Form.Validator]
+
+...
+*/
+
+Locale.define('cs-CZ', 'FormValidator', {
+
+ required: 'Tato poloşka je povinná.',
+ minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+ maxLength: 'Zadejte prosím méně neÅŸ {maxLength} znaků (nápsáno {length} znaků).',
+ integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.',
+ numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+ digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).',
+ alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+ alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+ dateSuchAs: 'Zadejte prosím platné datum jako {date}',
+ dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+ email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".',
+ url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.',
+ currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.',
+ oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto poloÅŸky.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornění: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V této poloşce nejsou povoleny mezery',
+ reqChkByNode: 'Nejsou vybrány şádné poloşky.',
+ requiredChk: 'Tato poloşka je vyşadována.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Tato poloşka se musí shodovat s poloşkou {matchName}',
+ startDate: 'datum zahájení',
+ endDate: 'datum ukončení',
+ currentDate: 'aktuální datum',
+ afterDate: 'Datum by mělo bÜt stejné nebo větší neÅŸ {label}.',
+ beforeDate: 'Datum by mělo bÜt stejné nebo menší neÅŸ {label}.',
+ startMonth: 'Vyberte počáteční měsíc.',
+ sameMonth: 'Tyto dva datumy musí bÜt ve stejném měsíci - změňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Date
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Date]
+
+...
+*/
+
+Locale.define('da-DK', 'Date', {
+
+ months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+ months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'mindre end et minut siden',
+ minuteAgo: 'omkring et minut siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omkring en time siden',
+ hoursAgo: 'omkring {delta} timer siden',
+ dayAgo: '1 dag siden',
+ daysAgo: '{delta} dage siden',
+ weekAgo: '1 uge siden',
+ weeksAgo: '{delta} uger siden',
+ monthAgo: '1 måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: '1 år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre end et minut fra nu',
+ minuteUntil: 'omkring et minut fra nu',
+ minutesUntil: '{delta} minutter fra nu',
+ hourUntil: 'omkring en time fra nu',
+ hoursUntil: 'omkring {delta} timer fra nu',
+ dayUntil: '1 dag fra nu',
+ daysUntil: '{delta} dage fra nu',
+ weekUntil: '1 uge fra nu',
+ weeksUntil: '{delta} uger fra nu',
+ monthUntil: '1 måned fra nu',
+ monthsUntil: '{delta} måneder fra nu',
+ yearUntil: '1 år fra nu',
+ yearsUntil: '{delta} år fra nu'
+
+});
+
+/*
+---
+
+name: Locale.da-DK.Form.Validator
+
+description: Form Validator messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+
+requires:
+ - Locale
+
+provides: [Locale.da-DK.Form.Validator]
+
+...
+*/
+
+Locale.define('da-DK', 'FormValidator', {
+
+ required: 'Feltet skal udfyldes.',
+ minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.',
+ numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").',
+ digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).',
+ alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.',
+ dateSuchAs: 'Skriv en gyldig dato som {date}',
+ dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")',
+ email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".',
+ url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".',
+ currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .',
+ oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.',
+ errorPrefix: 'Fejl: ',
+ warningPrefix: 'Advarsel: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Der må ikke benyttes mellemrum i dette felt.',
+ reqChkByNode: 'Foretag et valg.',
+ requiredChk: 'Dette felt skal udfyldes.',
+ reqChkByName: 'VÊlg en {label}.',
+ match: 'Dette felt skal matche {matchName} feltet',
+ startDate: 'start dato',
+ endDate: 'slut dato',
+ currentDate: 'dags dato',
+ afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.',
+ beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.',
+ startMonth: 'VÊlg en start måned',
+ sameMonth: 'De valgte datoer skal vÊre i samme måned - skift en af dem.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Date
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Date]
+
+...
+*/
+
+Locale.define('de-DE', 'Date', {
+
+ months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+ months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
+ days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+ days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'vormittags',
+ PM: 'nachmittags',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vor weniger als einer Minute',
+ minuteAgo: 'vor einer Minute',
+ minutesAgo: 'vor {delta} Minuten',
+ hourAgo: 'vor einer Stunde',
+ hoursAgo: 'vor {delta} Stunden',
+ dayAgo: 'vor einem Tag',
+ daysAgo: 'vor {delta} Tagen',
+ weekAgo: 'vor einer Woche',
+ weeksAgo: 'vor {delta} Wochen',
+ monthAgo: 'vor einem Monat',
+ monthsAgo: 'vor {delta} Monaten',
+ yearAgo: 'vor einem Jahr',
+ yearsAgo: 'vor {delta} Jahren',
+
+ lessThanMinuteUntil: 'in weniger als einer Minute',
+ minuteUntil: 'in einer Minute',
+ minutesUntil: 'in {delta} Minuten',
+ hourUntil: 'in ca. einer Stunde',
+ hoursUntil: 'in ca. {delta} Stunden',
+ dayUntil: 'in einem Tag',
+ daysUntil: 'in {delta} Tagen',
+ weekUntil: 'in einer Woche',
+ weeksUntil: 'in {delta} Wochen',
+ monthUntil: 'in einem Monat',
+ monthsUntil: 'in {delta} Monaten',
+ yearUntil: 'in einem Jahr',
+ yearsUntil: 'in {delta} Jahren'
+
+});
+
+/*
+---
+
+name: Locale.de-CH.Date
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+ - Locale.de-DE.Date
+
+provides: [Locale.de-CH.Date]
+
+...
+*/
+
+Locale.define('de-CH').inherit('de-DE', 'Date');
+
+/*
+---
+
+name: Locale.de-CH.Form.Validator
+
+description: Form Validator messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - Locale
+
+provides: [Locale.de-CH.Form.Validator]
+
+...
+*/
+
+Locale.define('de-CH', 'FormValidator', {
+
+ required: 'Dieses Feld ist obligatorisch.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+ numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+ digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+ alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+ dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+ email: 'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria@bernasconi.ch&quot;.',
+ url: 'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.example.com.',
+ currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+ oneRequired: 'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+ reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+ requiredChk: 'Dieses Feld ist obligatorisch.',
+ reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+ startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Form.Validator
+
+description: Form Validator messages for German.
+
+license: MIT-style license
+
+authors:
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - Locale
+
+provides: [Locale.de-DE.Form.Validator]
+
+...
+*/
+
+Locale.define('de-DE', 'FormValidator', {
+
+ required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.',
+ minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+ maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+ integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.',
+ numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.',
+ digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+ alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+ alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+ dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").',
+ dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").',
+ email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").',
+ url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").',
+ currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00€).',
+ oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.',
+ errorPrefix: 'Fehler: ',
+ warningPrefix: 'Warnung: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+ reqChkByNode: 'Es wurden keine Elemente gewÀhlt.',
+ requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.',
+ reqChkByName: 'Bitte wÀhlen Sie ein {label}.',
+ match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.',
+ startDate: 'Das Anfangsdatum',
+ endDate: 'Das Enddatum',
+ currentDate: 'Das aktuelle Datum',
+ afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.',
+ beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.',
+ startMonth: 'WÀhlen Sie bitte einen Anfangsmonat',
+ sameMonth: 'Diese zwei Datumsangaben mÌssen im selben Monat sein - Sie mÌssen eines von beiden verÀndern.',
+ creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+
+});
+
+/*
+---
+
+name: Locale.de-DE.Number
+
+description: Number messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.de-DE.Number]
+
+...
+*/
+
+Locale.define('de-DE').inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.el-GR.Date
+
+description: Date messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Periklis Argiriadis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Date]
+
+...
+*/
+
+Locale.define('el-GR', 'Date', {
+
+ months: ['ΙαΜουάριος', 'Ίεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'ΙούΜιος', 'Ιούλιος', 'Αύγουστος', 'ΣεπτέΌβριος', 'Οκτώβριος', 'ΝοέΌβριος', 'ΔεκέΌβριος'],
+ months_abbr: ['ΙαΜ', 'Ίεβ', 'Μαρ', 'Απρ', 'Μάι', 'ΙουΜ', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
+ days: ['Κυριακή', 'Δευτέρα', '΀ρίτη', '΀ετάρτη', 'ΠέΌπτη', 'Παρασκευή', 'Σάββατο'],
+ days_abbr: ['Κυρ', 'Δευ', '΀ρι', '΀ετ', 'ΠεΌ', 'Παρ', 'Σαβ'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'πΌ',
+ PM: 'ΌΌ',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ // 1st, 2nd, 3rd, etc.
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ος' : ['ος'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'λιγότερο από έΜα λεπτό πριΜ',
+ minuteAgo: 'περίπου έΜα λεπτό πριΜ',
+ minutesAgo: '{delta} λεπτά πριΜ',
+ hourAgo: 'περίπου Όια ώρα πριΜ',
+ hoursAgo: 'περίπου {delta} ώρες πριΜ',
+ dayAgo: '1 ηΌέρα πριΜ',
+ daysAgo: '{delta} ηΌέρες πριΜ',
+ weekAgo: '1 εβΎοΌάΎα πριΜ',
+ weeksAgo: '{delta} εβΎοΌάΎες πριΜ',
+ monthAgo: '1 ΌήΜα πριΜ',
+ monthsAgo: '{delta} ΌήΜες πριΜ',
+ yearAgo: '1 χρόΜο πριΜ',
+ yearsAgo: '{delta} χρόΜια πριΜ',
+
+ lessThanMinuteUntil: 'λιγότερο από λεπτό από τώρα',
+ minuteUntil: 'περίπου έΜα λεπτό από τώρα',
+ minutesUntil: '{delta} λεπτά από τώρα',
+ hourUntil: 'περίπου Όια ώρα από τώρα',
+ hoursUntil: 'περίπου {delta} ώρες από τώρα',
+ dayUntil: '1 ηΌέρα από τώρα',
+ daysUntil: '{delta} ηΌέρες από τώρα',
+ weekUntil: '1 εβΎοΌάΎα από τώρα',
+ weeksUntil: '{delta} εβΎοΌάΎες από τώρα',
+ monthUntil: '1 ΌήΜας από τώρα',
+ monthsUntil: '{delta} ΌήΜες από τώρα',
+ yearUntil: '1 χρόΜος από τώρα',
+ yearsUntil: '{delta} χρόΜια από τώρα'
+
+});
+
+/*
+---
+
+name: Locale.el-GR.Form.Validator
+
+description: Form Validator messages for Greek language.
+
+license: MIT-style license
+
+authors:
+ - Dimitris Tsironis
+
+requires:
+ - Locale
+
+provides: [Locale.el-GR.Form.Validator]
+
+...
+*/
+
+Locale.define('el-GR', 'FormValidator', {
+
+ required: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ length: 'ΠαρακαλούΌε, εισάγετε {length} χαρακτήρες (έχετε ήΎη εισάγει {elLength} χαρακτήρες).',
+ minLength: 'ΠαρακαλούΌε, εισάγετε τουλάχιστοΜ {minLength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ maxlength: 'ΠαρακαλούΌε, εισάγετε εώς {maxlength} χαρακτήρες (έχετε ήΎη εισάγε {length} χαρακτήρες).',
+ integer: 'ΠαρακαλούΌε, εισάγετε έΜαΜ ακέραιο αριΞΌό σε αυτό το πεΎίο. Οι αριΞΌοί Όε ΎεκαΎικά ψηφία (π.χ. 1.25) ΎεΜ επιτρέποΜται.',
+ numeric: 'ΠαρακαλούΌε, εισάγετε ΌόΜο αριΞΌητικές τιΌές σε αυτό το πεΎίο (π.χ." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).',
+ digits: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο αριΞΌούς και σηΌεία στίΟης σε αυτόΜ τοΜ τοΌέα (π.χ. επιτρέπεται αριΞΌός τηλεφώΜου Όε παύλες ή τελείες).',
+ alpha: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) σε αυτό το πεΎίο. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ alphanum: 'ΠαρακαλούΌε, χρησιΌοποιήστε ΌόΜο γράΌΌατα (a-z) ή αριΞΌούς (0-9) σε αυτόΜ τοΜ τοΌέα. ΔεΜ επιτρέποΜται κεΜά ή άλλοι χαρακτήρες.',
+ dateSuchAs: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως {date}',
+ dateInFormatMDY: 'Παρακαλώ εισάγετε Όια έγκυρη ηΌεροΌηΜία, όπως ΜΜ/ΗΗ/ΕΕΕΕ (π.χ. "12/31/1999").',
+ email: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη ΎιεύΞυΜση ηλεκτροΜικού ταχυΎροΌείου (π.χ. "fred@domain.com").',
+ url: 'ΠαρακαλούΌε, εισάγετε Όια έγκυρη URL ΎιεύΞυΜση, όπως http://www.example.com',
+ currencyDollar: 'ΠαρακαλούΌε, εισάγετε έΜα έγκυρο ποσό σε Ύολλάρια (π.χ. $100.00).',
+ oneRequired: 'ΠαρακαλούΌε, εισάγετε κάτι για τουλάχιστοΜ έΜα από αυτά τα πεΎία.',
+ errorPrefix: 'ΣφάλΌα: ',
+ warningPrefix: 'Προσοχή: ',
+
+ // Form.Validator.Extras
+ noSpace: 'ΔεΜ επιτρέποΜται τα κεΜά σε αυτό το πεΎίο.',
+ reqChkByNode: 'ΔεΜ έχει επιλεγεί κάποιο αΜτικείΌεΜο',
+ requiredChk: 'Αυτό το πεΎίο είΜαι απαραίτητο.',
+ reqChkByName: 'ΠαρακαλούΌε, επιλέΟτε Όια ετικέτα {label}.',
+ match: 'Αυτό το πεΎίο πρέπει Μα ταιριάζει Όε το πεΎίο {matchName}.',
+ startDate: 'η ηΌεροΌηΜία έΜαρΟης',
+ endDate: 'η ηΌεροΌηΜία λήΟης',
+ currentDate: 'η τρέχουσα ηΌεροΌηΜία',
+ afterDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή Όετά από τηΜ {label}.',
+ beforeDate: 'Η ηΌεροΌηΜία πρέπει Μα είΜαι η ίΎια ή πριΜ από τηΜ {label}.',
+ startMonth: 'Παρακαλώ επιλέΟτε έΜα ΌήΜα αρχής.',
+ sameMonth: 'Αυτές οι Ύύο ηΌεροΌηΜίες πρέπει Μα έχουΜ τοΜ ίΎιο ΌήΜα - Ξα πρέπει Μα αλλάΟετε ή το έΜα ή το άλλο',
+ creditcard: 'Ο αριΞΌός της πιστωτικής κάρτας ΎεΜ είΜαι έγκυρος. ΠαρακαλούΌε ελέγΟτε τοΜ αριΞΌό και ΎοκιΌάστε ΟαΜά. {length} Όήκος ψηφίωΜ.'
+
+});
+
+/*
+---
+
+name: Locale.en-GB.Date
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Locale
+ - Locale.en-US.Date
+
+provides: [Locale.en-GB.Date]
+
+...
+*/
+
+Locale.define('en-GB', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M'
+
+}).inherit('en-US', 'Date');
+
+/*
+---
+
+name: Locale.en-US.Number
+
+description: Number messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+
+provides: [Locale.en-US.Number]
+
+...
+*/
+
+Locale.define('en-US', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+/* Commented properties are the defaults for Number.format
+ decimals: 0,
+ precision: 0,
+ scientific: null,
+
+ prefix: null,
+ suffic: null,
+
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },*/
+
+ currency: {
+// decimals: 2,
+ prefix: '$ '
+ }/*,
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }*/
+
+});
+
+
+
+/*
+---
+
+name: Locale.es-ES.Date
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Date]
+
+...
+*/
+
+Locale.define('es-ES', 'Date', {
+
+ months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+ months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
+ days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+ days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'hace menos de un minuto',
+ minuteAgo: 'hace un minuto',
+ minutesAgo: 'hace {delta} minutos',
+ hourAgo: 'hace una hora',
+ hoursAgo: 'hace unas {delta} horas',
+ dayAgo: 'hace un día',
+ daysAgo: 'hace {delta} días',
+ weekAgo: 'hace una semana',
+ weeksAgo: 'hace unas {delta} semanas',
+ monthAgo: 'hace un mes',
+ monthsAgo: 'hace {delta} meses',
+ yearAgo: 'hace un año',
+ yearsAgo: 'hace {delta} años',
+
+ lessThanMinuteUntil: 'menos de un minuto desde ahora',
+ minuteUntil: 'un minuto desde ahora',
+ minutesUntil: '{delta} minutos desde ahora',
+ hourUntil: 'una hora desde ahora',
+ hoursUntil: 'unas {delta} horas desde ahora',
+ dayUntil: 'un día desde ahora',
+ daysUntil: '{delta} días desde ahora',
+ weekUntil: 'una semana desde ahora',
+ weeksUntil: 'unas {delta} semanas desde ahora',
+ monthUntil: 'un mes desde ahora',
+ monthsUntil: '{delta} meses desde ahora',
+ yearUntil: 'un año desde ahora',
+ yearsUntil: '{delta} años desde ahora'
+
+});
+
+/*
+---
+
+name: Locale.es-AR.Date
+
+description: Date messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+ - Diego Massanti
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-AR.Date]
+
+...
+*/
+
+Locale.define('es-AR').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-AR.Form.Validator
+
+description: Form Validator messages for Spanish (Argentina).
+
+license: MIT-style license
+
+authors:
+ - Diego Massanti
+
+requires:
+ - Locale
+
+provides: [Locale.es-AR.Form.Validator]
+
+...
+*/
+
+Locale.define('es-AR', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).',
+ maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).',
+ integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).',
+ alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.',
+ dateSuchAs: 'Por favor ingrese una fecha válida como {date}',
+ dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".',
+ url: 'Por favor ingrese una URL válida como http://www.example.com.',
+ currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .',
+ oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Advertencia: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No se permiten espacios en este campo.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-ES.Form.Validator
+
+description: Form Validator messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - Locale
+
+provides: [Locale.es-ES.Form.Validator]
+
+...
+*/
+
+Locale.define('es-ES', 'FormValidator', {
+
+ required: 'Este campo es obligatorio.',
+ minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+ maxLength: 'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+ integer: 'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+ numeric: 'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+ alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ alphanum: 'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+ dateSuchAs: 'Por favor introduce una fecha v&aacute;lida como {date}',
+ dateInFormatMDY: 'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+ email: 'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo, "fred@domain.com".',
+ url: 'Por favor introduce una URL v&aacute;lida como http://www.example.com.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+ oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.',
+ errorPrefix: 'Error: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'No pueden haber espacios en esta entrada.',
+ reqChkByNode: 'No hay elementos seleccionados.',
+ requiredChk: 'Este campo es obligatorio.',
+ reqChkByName: 'Por favor selecciona una {label}.',
+ match: 'Este campo necesita coincidir con el campo {matchName}',
+ startDate: 'la fecha de inicio',
+ endDate: 'la fecha de fin',
+ currentDate: 'la fecha actual',
+ afterDate: 'La fecha debe ser igual o posterior a {label}.',
+ beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+ startMonth: 'Por favor selecciona un mes de origen',
+ sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+
+/*
+---
+
+name: Locale.es-VE.Date
+
+description: Date messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Date
+
+provides: [Locale.es-VE.Date]
+
+...
+*/
+
+Locale.define('es-VE').inherit('es-ES', 'Date');
+
+/*
+---
+
+name: Locale.es-VE.Form.Validator
+
+description: Form Validator messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+ - Locale.es-ES.Form.Validator
+
+provides: [Locale.es-VE.Form.Validator]
+
+...
+*/
+
+Locale.define('es-VE', 'FormValidator', {
+
+ digits: 'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo. Por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido.',
+ alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+ currencyDollar: 'Por favor introduce una cantidad v&aacute;lida de Bs. Por ejemplo Bs. 100,00 .',
+ oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.',
+
+ // Form.Validator.Extras
+ startDate: 'La fecha de inicio',
+ endDate: 'La fecha de fin',
+ currentDate: 'La fecha actual'
+
+}).inherit('es-ES', 'FormValidator');
+
+/*
+---
+
+name: Locale.es-VE.Number
+
+description: Number messages for Spanish (Venezuela).
+
+license: MIT-style license
+
+authors:
+ - Daniel Barreto
+
+requires:
+ - Locale
+
+provides: [Locale.es-VE.Number]
+
+...
+*/
+
+Locale.define('es-VE', 'Number', {
+
+ decimal: ',',
+ group: '.',
+/*
+ decimals: 0,
+ precision: 0,
+*/
+ // Negative/Currency/percentage will mixin Number
+ negative: {
+ prefix: '-'
+ },
+
+ currency: {
+ decimals: 2,
+ prefix: 'Bs. '
+ },
+
+ percentage: {
+ decimals: 2,
+ suffix: '%'
+ }
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Date
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Date]
+
+...
+*/
+
+Locale.define('et-EE', 'Date', {
+
+ months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+ months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'],
+ days: ['pÌhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'],
+ days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'],
+
+ // Culture's date order: MM.DD.YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m.%d.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'vÀhem kui minut aega tagasi',
+ minuteAgo: 'umbes minut aega tagasi',
+ minutesAgo: '{delta} minutit tagasi',
+ hourAgo: 'umbes tund aega tagasi',
+ hoursAgo: 'umbes {delta} tundi tagasi',
+ dayAgo: '1 pÀev tagasi',
+ daysAgo: '{delta} pÀeva tagasi',
+ weekAgo: '1 nÀdal tagasi',
+ weeksAgo: '{delta} nÀdalat tagasi',
+ monthAgo: '1 kuu tagasi',
+ monthsAgo: '{delta} kuud tagasi',
+ yearAgo: '1 aasta tagasi',
+ yearsAgo: '{delta} aastat tagasi',
+
+ lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast',
+ minuteUntil: 'umbes minuti aja pÀrast',
+ minutesUntil: '{delta} minuti pÀrast',
+ hourUntil: 'umbes tunni aja pÀrast',
+ hoursUntil: 'umbes {delta} tunni pÀrast',
+ dayUntil: '1 pÀeva pÀrast',
+ daysUntil: '{delta} pÀeva pÀrast',
+ weekUntil: '1 nÀdala pÀrast',
+ weeksUntil: '{delta} nÀdala pÀrast',
+ monthUntil: '1 kuu pÀrast',
+ monthsUntil: '{delta} kuu pÀrast',
+ yearUntil: '1 aasta pÀrast',
+ yearsUntil: '{delta} aasta pÀrast'
+
+});
+
+/*
+---
+
+name: Locale.et-EE.Form.Validator
+
+description: Form Validator messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - Locale
+
+provides: [Locale.et-EE.Form.Validator]
+
+...
+*/
+
+Locale.define('et-EE', 'FormValidator', {
+
+ required: 'VÀli peab olema tÀidetud.',
+ minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).',
+ maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).',
+ integer: 'Palun sisestage vÀljale tÀisarv. KÌmnendarvud (nÀiteks 1.25) ei ole lubatud.',
+ numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").',
+ digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+ alpha: 'Palun kasutage ainult tÀhti (a-z). TÌhikud ja teised sÌmbolid on keelatud.',
+ alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÌhikud ja teised sÌmbolid on keelatud.',
+ dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}',
+ dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").',
+ email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").',
+ url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).',
+ currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).',
+ oneRequired: 'Palun sisestage midagi vÀhemalt Ìhele antud vÀljadest.',
+ errorPrefix: 'Viga: ',
+ warningPrefix: 'Hoiatus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'VÀli ei tohi sisaldada tÌhikuid.',
+ reqChkByNode: 'Ükski vÀljadest pole valitud.',
+ requiredChk: 'VÀlja tÀitmine on vajalik.',
+ reqChkByName: 'Palun valige ÃŒks {label}.',
+ match: 'VÀli peab sobima {matchName} vÀljaga',
+ startDate: 'algkuupÀev',
+ endDate: 'lõppkuupÀev',
+ currentDate: 'praegune kuupÀev',
+ afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.',
+ beforeDate: 'KuupÀev peab olema võrdne või enne {label}.',
+ startMonth: 'Palun valige algkuupÀev.',
+ sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma Ìhte kuupÀeva.'
+
+});
+
+/*
+---
+
+name: Locale.fa.Date
+
+description: Date messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Date]
+
+...
+*/
+
+Locale.define('fa', 'Date', {
+
+ months: ['ژانویه', 'فوریه', 'مارس', 'آٟریل', 'مه', 'ژو؊ن', 'ژو؊یه', 'آگوست', 'سٟتامؚر', 'اکتؚر', 'نوامؚر', 'دسامؚر'],
+ months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
+ days: ['یک؎نؚه', 'دو؎نؚه', 'سه ؎نؚه', 'چهار؎نؚه', 'ٟنج؎نؚه', 'جمعه', '؎نؚه'],
+ days_abbr: ['ي', 'د', 'س', 'چ', 'ÙŸ', 'ج', 'ØŽ'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['month', 'date', 'year'],
+ shortDate: '%m/%d/%Y',
+ shortTime: '%I:%M%p',
+ AM: 'ق.Øž',
+ PM: 'Øš.Øž',
+
+ // Date.Extras
+ ordinal: 'ام',
+
+ lessThanMinuteAgo: 'کمتر از یک دقیقه ٟی؎',
+ minuteAgo: 'حدود یک دقیقه ٟی؎',
+ minutesAgo: '{delta} دقیقه ٟی؎',
+ hourAgo: 'حدود یک ساعت ٟی؎',
+ hoursAgo: 'حدود {delta} ساعت ٟی؎',
+ dayAgo: '1 روز ٟی؎',
+ daysAgo: '{delta} روز ٟی؎',
+ weekAgo: '1 هفته ٟی؎',
+ weeksAgo: '{delta} هفته ٟی؎',
+ monthAgo: '1 ماه ٟی؎',
+ monthsAgo: '{delta} ماه ٟی؎',
+ yearAgo: '1 سال ٟی؎',
+ yearsAgo: '{delta} سال ٟی؎',
+
+ lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا',
+ minuteUntil: 'حدود یک دقیقه از حالا',
+ minutesUntil: '{delta} دقیقه از حالا',
+ hourUntil: 'حدود یک ساعت از حالا',
+ hoursUntil: 'حدود {delta} ساعت از حالا',
+ dayUntil: '1 روز از حالا',
+ daysUntil: '{delta} روز از حالا',
+ weekUntil: '1 هفته از حالا',
+ weeksUntil: '{delta} هفته از حالا',
+ monthUntil: '1 ماه از حالا',
+ monthsUntil: '{delta} ماه از حالا',
+ yearUntil: '1 سال از حالا',
+ yearsUntil: '{delta} سال از حالا'
+
+});
+
+/*
+---
+
+name: Locale.fa.Form.Validator
+
+description: Form Validator messages for Persian.
+
+license: MIT-style license
+
+authors:
+ - Amir Hossein Hodjaty Pour
+
+requires:
+ - Locale
+
+provides: [Locale.fa.Form.Validator]
+
+...
+*/
+
+Locale.define('fa', 'FormValidator', {
+
+ required: 'این فیلد الزامی است.',
+ minLength: '؎ما ؚاید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).',
+ maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (؎ما {length} حرف وارد کرده اید).',
+ integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اع؎اری (مانند 1.25) مجاز نیستند.',
+ numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").',
+ digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (ؚرای مثال ؎ماره تلفن ؚا خط تیره و نقطه قاؚل قؚول است).',
+ alpha: 'لطفا فقط از حروف الفؚاء ؚرای این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ alphanum: 'لطفا فقط از حروف الفؚاء و اعداد در این ؚخ؎ استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.',
+ dateSuchAs: 'لطفا یک تاریخ معتؚر مانند {date} وارد کنید.',
+ dateInFormatMDY: 'لطفا یک تاریخ معتؚر ØšÙ‡ ØŽÚ©Ù„ MM/DD/YYYY وارد کنید (مانند "12/31/1999").',
+ email: 'لطفا یک آدرس ایمیل معتؚر وارد کنید. ؚرای مثال "fred@domain.com".',
+ url: 'لطفا یک URL معتؚر مانند http://www.example.com وارد کنید.',
+ currencyDollar: 'لطفا یک محدوده معتؚر ؚرای این ؚخ؎ وارد کنید مانند 100.00$ .',
+ oneRequired: 'لطفا حداقل یکی از فیلدها را ٟر کنید.',
+ errorPrefix: 'خطا: ',
+ warningPrefix: 'ه؎دار: ',
+
+ // Form.Validator.Extras
+ noSpace: 'استفاده از فاصله در این ؚخ؎ مجاز نیست.',
+ reqChkByNode: 'موردی انتخاؚ ن؎ده است.',
+ requiredChk: 'این فیلد الزامی است.',
+ reqChkByName: 'لطفا یک {label} را انتخاؚ کنید.',
+ match: 'این فیلد ؚاید ؚا فیلد {matchName} مطاؚقت دا؎ته ؚا؎د.',
+ startDate: 'تاریخ ؎روع',
+ endDate: 'تاریخ ٟایان',
+ currentDate: 'تاریخ کنونی',
+ afterDate: 'تاریخ میؚایست ؚراؚر یا ؚعد از {label} ؚا؎د',
+ beforeDate: 'تاریخ میؚایست ؚراؚر یا Ù‚ØšÙ„ از {label} ؚا؎د',
+ startMonth: 'لطفا ماه ؎روع را انتخاؚ کنید',
+ sameMonth: 'این دو تاریخ ؚاید در یک ماه ؚا؎ند - ؎ما ؚاید یکی یا هر دو را تغییر دهید.',
+ creditcard: '؎ماره کارت اعتؚاری که وارد کرده اید معتؚر نیست. لطفا ؎ماره را ؚررسی کنید و مجددا تلا؎ کنید. {length} رقم وارد ؎ده است.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Date
+
+description: Date messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Date]
+
+...
+*/
+
+Locale.define('fi-FI', 'Date', {
+
+ // NOTE: months and days are not capitalized in finnish
+ months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
+
+ // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;)
+ // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish.
+ months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'],
+
+ days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'],
+ days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'vajaa minuutti sitten',
+ minuteAgo: 'noin minuutti sitten',
+ minutesAgo: '{delta} minuuttia sitten',
+ hourAgo: 'noin tunti sitten',
+ hoursAgo: 'noin {delta} tuntia sitten',
+ dayAgo: 'pÀivÀ sitten',
+ daysAgo: '{delta} pÀivÀÀ sitten',
+ weekAgo: 'viikko sitten',
+ weeksAgo: '{delta} viikkoa sitten',
+ monthAgo: 'kuukausi sitten',
+ monthsAgo: '{delta} kuukautta sitten',
+ yearAgo: 'vuosi sitten',
+ yearsAgo: '{delta} vuotta sitten',
+
+ lessThanMinuteUntil: 'vajaan minuutin kuluttua',
+ minuteUntil: 'noin minuutin kuluttua',
+ minutesUntil: '{delta} minuutin kuluttua',
+ hourUntil: 'noin tunnin kuluttua',
+ hoursUntil: 'noin {delta} tunnin kuluttua',
+ dayUntil: 'pÀivÀn kuluttua',
+ daysUntil: '{delta} pÀivÀn kuluttua',
+ weekUntil: 'viikon kuluttua',
+ weeksUntil: '{delta} viikon kuluttua',
+ monthUntil: 'kuukauden kuluttua',
+ monthsUntil: '{delta} kuukauden kuluttua',
+ yearUntil: 'vuoden kuluttua',
+ yearsUntil: '{delta} vuoden kuluttua'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Form.Validator
+
+description: Form Validator messages for Finnish.
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+
+provides: [Locale.fi-FI.Form.Validator]
+
+...
+*/
+
+Locale.define('fi-FI', 'FormValidator', {
+
+ required: 'TÀmÀ kenttÀ on pakollinen.',
+ minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).',
+ maxLength: 'ÄlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).',
+ integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.',
+ numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").',
+ digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).',
+ alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.',
+ dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}',
+ dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")',
+ email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").',
+ url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.',
+ currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .',
+ oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.',
+ errorPrefix: 'Virhe: ',
+ warningPrefix: 'Varoitus: ',
+
+ // Form.Validator.Extras
+ noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ',
+ reqChkByNode: 'Ei valintoja.',
+ requiredChk: 'TÀmÀ kenttÀ on pakollinen.',
+ reqChkByName: 'Ole hyvÀ ja valitse {label}.',
+ match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}',
+ startDate: 'alkupÀivÀmÀÀrÀ',
+ endDate: 'loppupÀivÀmÀÀrÀ',
+ currentDate: 'nykyinen pÀivÀmÀÀrÀ',
+ afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.',
+ beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.',
+ startMonth: 'Ole hyvÀ ja valitse aloituskuukausi',
+ sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.',
+ creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.'
+
+});
+
+/*
+---
+
+name: Locale.fi-FI.Number
+
+description: Finnish number messages
+
+license: MIT-style license
+
+authors:
+ - ksel
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fi-FI.Number]
+
+...
+*/
+
+Locale.define('fi-FI', 'Number', {
+
+ group: ' ' // grouped by space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.fr-FR.Date
+
+description: Date messages for French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Date]
+
+...
+*/
+
+Locale.define('fr-FR', 'Date', {
+
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
+ months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
+ days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
+ days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 1) ? '' : 'er';
+ },
+
+ lessThanMinuteAgo: "il y a moins d'une minute",
+ minuteAgo: 'il y a une minute',
+ minutesAgo: 'il y a {delta} minutes',
+ hourAgo: 'il y a une heure',
+ hoursAgo: 'il y a {delta} heures',
+ dayAgo: 'il y a un jour',
+ daysAgo: 'il y a {delta} jours',
+ weekAgo: 'il y a une semaine',
+ weeksAgo: 'il y a {delta} semaines',
+ monthAgo: 'il y a 1 mois',
+ monthsAgo: 'il y a {delta} mois',
+ yearthAgo: 'il y a 1 an',
+ yearsAgo: 'il y a {delta} ans',
+
+ lessThanMinuteUntil: "dans moins d'une minute",
+ minuteUntil: 'dans une minute',
+ minutesUntil: 'dans {delta} minutes',
+ hourUntil: 'dans une heure',
+ hoursUntil: 'dans {delta} heures',
+ dayUntil: 'dans un jour',
+ daysUntil: 'dans {delta} jours',
+ weekUntil: 'dans 1 semaine',
+ weeksUntil: 'dans {delta} semaines',
+ monthUntil: 'dans 1 mois',
+ monthsUntil: 'dans {delta} mois',
+ yearUntil: 'dans 1 an',
+ yearsUntil: 'dans {delta} ans'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Form.Validator
+
+description: Form Validator messages for French.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Nicolas Sorosac
+
+requires:
+ - Locale
+
+provides: [Locale.fr-FR.Form.Validator]
+
+...
+*/
+
+Locale.define('fr-FR', 'FormValidator', {
+
+ required: 'Ce champ est obligatoire.',
+ length: 'Veuillez saisir {length} caract&egrave;re(s) (vous avez saisi {elLength} caract&egrave;re(s)',
+ minLength: 'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ maxLength: 'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+ integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+ numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+ digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d'union est autoris&eacute;).",
+ alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+ dateSuchAs: 'Veuillez saisir une date correcte comme {date}',
+ dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+ email: 'Veuillez saisir une adresse de courrier &eacute;lectronique. Par exemple "fred@domaine.com".',
+ url: 'Veuillez saisir une URL, comme http://www.exemple.com.',
+ currencyDollar: 'Veuillez saisir une quantit&eacute; correcte. Par exemple 100,00&euro;.',
+ oneRequired: 'Veuillez s&eacute;lectionner au moins une de ces options.',
+ errorPrefix: 'Erreur : ',
+ warningPrefix: 'Attention : ',
+
+ // Form.Validator.Extras
+ noSpace: "Ce champ n'accepte pas les espaces.",
+ reqChkByNode: "Aucun &eacute;l&eacute;ment n'est s&eacute;lectionn&eacute;.",
+ requiredChk: 'Ce champ est obligatoire.',
+ reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+ match: 'Ce champ doit correspondre avec le champ {matchName}.',
+ startDate: 'date de d&eacute;but',
+ endDate: 'date de fin',
+ currentDate: 'date actuelle',
+ afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+ beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+ startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+ sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.',
+ creditcard: 'Le num&eacute;ro de carte de cr&eacute;dit est invalide. Merci de v&eacute;rifier le num&eacute;ro et de r&eacute;essayer. Vous avez entr&eacute; {length} chiffre(s).'
+
+});
+
+/*
+---
+
+name: Locale.fr-FR.Number
+
+description: Number messages for French.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - sv1l
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.fr-FR.Number]
+
+...
+*/
+
+Locale.define('fr-FR', 'Number', {
+
+ group: ' ' // In fr-FR localization, group character is a blank space
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.he-IL.Date
+
+description: Date messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Date]
+
+...
+*/
+
+Locale.define('he-IL', 'Date', {
+
+ months: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ months_abbr: ['ינוא׹', '׀בךואך', 'מךץ', 'א׀ךיל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ס׀טמבך', 'אוקטוב׹', 'נובמב׹', 'דשמב׹'],
+ days: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+ days_abbr: ['ךאשון', 'שני', 'שלישי', 'ךביעי', 'חמישי', 'שישי', 'שבת'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ל׀ני ׀חות מדקה',
+ minuteAgo: 'ל׀ני כדקה',
+ minutesAgo: 'ל׀ני {delta} דקות',
+ hourAgo: 'ל׀ני כשעה',
+ hoursAgo: 'ל׀ני {delta} שעות',
+ dayAgo: 'ל׀ני יום',
+ daysAgo: 'ל׀ני {delta} ימים',
+ weekAgo: 'ל׀ני שבוע',
+ weeksAgo: 'ל׀ני {delta} שבועות',
+ monthAgo: 'ל׀ני חודש',
+ monthsAgo: 'ל׀ני {delta} חודשים',
+ yearAgo: 'ל׀ני שנה',
+ yearsAgo: 'ל׀ני {delta} שנים',
+
+ lessThanMinuteUntil: 'בעוד ׀חות מדקה',
+ minuteUntil: 'בעוד כדקה',
+ minutesUntil: 'בעוד {delta} דקות',
+ hourUntil: 'בעוד כשעה',
+ hoursUntil: 'בעוד {delta} שעות',
+ dayUntil: 'בעוד יום',
+ daysUntil: 'בעוד {delta} ימים',
+ weekUntil: 'בעוד שבוע',
+ weeksUntil: 'בעוד {delta} שבועות',
+ monthUntil: 'בעוד חודש',
+ monthsUntil: 'בעוד {delta} חודשים',
+ yearUntil: 'בעוד שנה',
+ yearsUntil: 'בעוד {delta} שנים'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Form.Validator
+
+description: Form Validator messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Form.Validator]
+
+...
+*/
+
+Locale.define('he-IL', 'FormValidator', {
+
+ required: 'נא למלא שדה זה.',
+ minLength: 'נא להזין ל׀חות {minLength} תווים (הזנת {length} תווים).',
+ maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).',
+ integer: 'נא להזין מס׀ך שלם לשדה זה. מס׀ךים עשךוניים (כמו 1.25) אינם חוקיים.',
+ numeric: 'נא להזין עךך מס׀ךי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").',
+ digits: 'נא להזין ךק ס׀ךות וסימני ה׀ךדה בשדה זה (למשל, מס׀ך טל׀ון עם מק׀ים או נקודות הוא חוקי).',
+ alpha: 'נא להזין ךק אותיות באנגלית (a-z) בשדה זה. ׹ווחים או תווים אח׹ים אינם חוקיים.',
+ alphanum: 'נא להזין ךק אותךיות באנגלית (a-z) או ס׀ךות (0-9) בשדה זה. אווח׹ים או תווים אח׹ים אינם חוקיים.',
+ dateSuchAs: 'נא להזין תאךיך חוקי, כמו {date}',
+ dateInFormatMDY: 'נא להזין תאךיך חוקי ב׀וךמט MM/DD/YYYY (כמו "12/31/1999")',
+ email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".',
+ url: 'נא להזין כתובת אתך חוקית, כמו http://www.example.com.',
+ currencyDollar: 'נא להזין סכום דול׹י חוקי. לדוגמה $100.00.',
+ oneRequired: 'נא לבחו׹ ל׀חות בשדה אחד.',
+ errorPrefix: 'שגיאה: ',
+ warningPrefix: 'אזה׹ה: ',
+
+ // Form.Validator.Extras
+ noSpace: 'אין להזין ׹ווחים בשדה זה.',
+ reqChkByNode: 'נא לבחו׹ אחת מהא׀שךויות.',
+ requiredChk: 'שדה זה נדךש.',
+ reqChkByName: 'נא לבחו׹ {label}.',
+ match: 'שדה זה ש׹יך להתאים לשדה {matchName}',
+ startDate: 'תאךיך ההתחלה',
+ endDate: 'תאךיך הסיום',
+ currentDate: 'התאךיך הנוכחי',
+ afterDate: 'התאךיך ש׹יך להיות זהה או אח׹י {label}.',
+ beforeDate: 'התאךיך ש׹יך להיות זהה או ל׀ני {label}.',
+ startMonth: 'נא לבחו׹ חודש התחלה',
+ sameMonth: 'שני תאךיכים אלה ש׹יכים להיות באותו חודש - נא לשנות אחד התאךיכים.',
+ creditcard: 'מס׀ך כךטיס האשךאי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ס׀ךות.'
+
+});
+
+/*
+---
+
+name: Locale.he-IL.Number
+
+description: Number messages for Hebrew.
+
+license: MIT-style license
+
+authors:
+ - Elad Ossadon
+
+requires:
+ - Locale
+
+provides: [Locale.he-IL.Number]
+
+...
+*/
+
+Locale.define('he-IL', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ suffix: ' ₪'
+ }
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Date
+
+description: Date messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Date]
+
+...
+*/
+
+Locale.define('hu-HU', 'Date', {
+
+ months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
+ months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'],
+ days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'],
+ days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
+
+ // Culture's date order: YYYY.MM.DD.
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y.%m.%d.',
+ shortTime: '%I:%M',
+ AM: 'de.',
+ PM: 'du.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'alig egy perce',
+ minuteAgo: 'egy perce',
+ minutesAgo: '{delta} perce',
+ hourAgo: 'egy órája',
+ hoursAgo: '{delta} órája',
+ dayAgo: '1 napja',
+ daysAgo: '{delta} napja',
+ weekAgo: '1 hete',
+ weeksAgo: '{delta} hete',
+ monthAgo: '1 hónapja',
+ monthsAgo: '{delta} hónapja',
+ yearAgo: '1 éve',
+ yearsAgo: '{delta} éve',
+
+ lessThanMinuteUntil: 'alig egy perc múlva',
+ minuteUntil: 'egy perc múlva',
+ minutesUntil: '{delta} perc múlva',
+ hourUntil: 'egy óra múlva',
+ hoursUntil: '{delta} óra múlva',
+ dayUntil: '1 nap múlva',
+ daysUntil: '{delta} nap múlva',
+ weekUntil: '1 hét múlva',
+ weeksUntil: '{delta} hét múlva',
+ monthUntil: '1 hónap múlva',
+ monthsUntil: '{delta} hónap múlva',
+ yearUntil: '1 év múlva',
+ yearsUntil: '{delta} év múlva'
+
+});
+
+/*
+---
+
+name: Locale.hu-HU.Form.Validator
+
+description: Form Validator messages for Hungarian.
+
+license: MIT-style license
+
+authors:
+ - Zsolt Szegheő
+
+requires:
+ - Locale
+
+provides: [Locale.hu-HU.Form.Validator]
+
+...
+*/
+
+Locale.define('hu-HU', 'FormValidator', {
+
+ required: 'A mező kitöltése kötelező.',
+ minLength: 'Legalább {minLength} karakter megadása szÌkséges (megadva {length} karakter).',
+ maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).',
+ integer: 'Egész szám megadása szÌkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.',
+ numeric: 'Szám megadása szÌkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").',
+ digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).',
+ alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.',
+ dateSuchAs: 'Valós dátum megadása szÌkséges (pl. {date}).',
+ dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")',
+ email: 'Valós e-mail cím megadása szÌkséges (pl. "fred@domain.hu").',
+ url: 'Valós URL megadása szÌkséges (pl. http://www.example.com).',
+ currencyDollar: 'Valós pénzösszeg megadása szÌkséges (pl. 100.00 Ft.).',
+ oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.',
+ errorPrefix: 'Hiba: ',
+ warningPrefix: 'Figyelem: ',
+
+ // Form.Validator.Extras
+ noSpace: 'A mező nem tartalmazhat szóközöket.',
+ reqChkByNode: 'Nincs egyetlen kijelölt elem sem.',
+ requiredChk: 'A mező kitöltése kötelező.',
+ reqChkByName: 'Egy {label} kiválasztása szÌkséges.',
+ match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.',
+ startDate: 'a kezdet dátuma',
+ endDate: 'a vég dátuma',
+ currentDate: 'jelenlegi dátum',
+ afterDate: 'A dátum nem lehet kisebb, mint {label}.',
+ beforeDate: 'A dátum nem lehet nagyobb, mint {label}.',
+ startMonth: 'Kezdeti hónap megadása szÌkséges.',
+ sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.',
+ creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Date
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Date]
+
+...
+*/
+
+Locale.define('it-IT', 'Date', {
+
+ months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+ months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
+ days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
+ days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'meno di un minuto fa',
+ minuteAgo: 'circa un minuto fa',
+ minutesAgo: 'circa {delta} minuti fa',
+ hourAgo: "circa un'ora fa",
+ hoursAgo: 'circa {delta} ore fa',
+ dayAgo: 'circa 1 giorno fa',
+ daysAgo: 'circa {delta} giorni fa',
+ weekAgo: 'una settimana fa',
+ weeksAgo: '{delta} settimane fa',
+ monthAgo: 'un mese fa',
+ monthsAgo: '{delta} mesi fa',
+ yearAgo: 'un anno fa',
+ yearsAgo: '{delta} anni fa',
+
+ lessThanMinuteUntil: 'tra meno di un minuto',
+ minuteUntil: 'tra circa un minuto',
+ minutesUntil: 'tra circa {delta} minuti',
+ hourUntil: "tra circa un'ora",
+ hoursUntil: 'tra circa {delta} ore',
+ dayUntil: 'tra circa un giorno',
+ daysUntil: 'tra circa {delta} giorni',
+ weekUntil: 'tra una settimana',
+ weeksUntil: 'tra {delta} settimane',
+ monthUntil: 'tra un mese',
+ monthsUntil: 'tra {delta} mesi',
+ yearUntil: 'tra un anno',
+ yearsUntil: 'tra {delta} anni'
+
+});
+
+/*
+---
+
+name: Locale.it-IT.Form.Validator
+
+description: Form Validator messages for Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - Locale
+
+provides: [Locale.it-IT.Form.Validator]
+
+...
+*/
+
+Locale.define('it-IT', 'FormValidator', {
+
+ required: 'Il campo &egrave; obbligatorio.',
+ minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+ maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+ integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+ numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+ digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+ alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+ alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+ dateSuchAs: 'Inserire una data valida del tipo {date}',
+ dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+ email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".',
+ url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".',
+ currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".',
+ oneRequired: 'Completare almeno uno dei campi richiesti.',
+ errorPrefix: 'Errore: ',
+ warningPrefix: 'Attenzione: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Non sono consentiti spazi.',
+ reqChkByNode: 'Nessuna voce selezionata.',
+ requiredChk: 'Il campo &egrave; obbligatorio.',
+ reqChkByName: 'Selezionare un(a) {label}.',
+ match: 'Il valore deve corrispondere al campo {matchName}',
+ startDate: "data d'inizio",
+ endDate: 'data di fine',
+ currentDate: 'data attuale',
+ afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+ beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+ startMonth: "Selezionare un mese d'inizio",
+ sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Date
+
+description: Date messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Date]
+
+...
+*/
+
+Locale.define('ja-JP', 'Date', {
+
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ days: ['日曜日', '月曜日', '火曜日', '氎曜日', '朚曜日', '金曜日', '土曜日'],
+ days_abbr: ['日', '月', '火', 'æ°Ž', '朚', '金', '土'],
+
+ // Culture's date order: YYYY/MM/DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y/%m/%d',
+ shortTime: '%H:%M',
+ AM: '午前',
+ PM: '午埌',
+ firstDayOfWeek: 0,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '1分以内前',
+ minuteAgo: '箄1分前',
+ minutesAgo: '箄{delta}分前',
+ hourAgo: '箄1時間前',
+ hoursAgo: '箄{delta}時間前',
+ dayAgo: '1日前',
+ daysAgo: '{delta}日前',
+ weekAgo: '1週間前',
+ weeksAgo: '{delta}週間前',
+ monthAgo: '1ヶ月前',
+ monthsAgo: '{delta}ヶ月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '今から玄1分以内',
+ minuteUntil: '今から玄1分',
+ minutesUntil: '今から玄{delta}分',
+ hourUntil: '今から玄1時間',
+ hoursUntil: '今から玄{delta}時間',
+ dayUntil: '今から1日間',
+ daysUntil: '今から{delta}日間',
+ weekUntil: '今から1週間',
+ weeksUntil: '今から{delta}週間',
+ monthUntil: '今から1ヶ月',
+ monthsUntil: '今から{delta}ヶ月',
+ yearUntil: '今から1幎',
+ yearsUntil: '今から{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Form.Validator
+
+description: Form Validator messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Form.Validator]
+
+...
+*/
+
+Locale.define("ja-JP", "FormValidator", {
+
+ required: '入力は必須です。',
+ minLength: '入力文字数は{minLength}以䞊にしおください。({length}文字)',
+ maxLength: '入力文字数は{maxLength}以䞋にしおください。({length}文字)',
+ integer: '敎数を入力しおください。',
+ numeric: '入力できるのは数倀だけです。(䟋: "1", "1.1", "-1", "-1.1"....)',
+ digits: '入力できるのは数倀ず句読蚘号です。 (䟋: -や+を含む電話番号など).',
+ alpha: '入力できるのは半角英字だけです。それ以倖の文字は入力できたせん。',
+ alphanum: '入力できるのは半角英数字だけです。それ以倖の文字は入力できたせん。',
+ dateSuchAs: '有効な日付を入力しおください。{date}',
+ dateInFormatMDY: '日付の曞匏に誀りがありたす。YYYY/MM/DD (i.e. "1999/12/31")',
+ email: 'メヌルアドレスに誀りがありたす。',
+ url: 'URLアドレスに誀りがありたす。',
+ currencyDollar: '金額に誀りがありたす。',
+ oneRequired: 'ひず぀以䞊入力しおください。',
+ errorPrefix: '゚ラヌ: ',
+ warningPrefix: '譊告: ',
+
+ // FormValidator.Extras
+ noSpace: 'スペヌスは入力できたせん。',
+ reqChkByNode: '遞択されおいたせん。',
+ requiredChk: 'この項目は必須です。',
+ reqChkByName: '{label}を遞択しおください。',
+ match: '{matchName}が入力されおいる堎合必須です。',
+ startDate: '開始日',
+ endDate: '終了日',
+ currentDate: '今日',
+ afterDate: '{label}以降の日付にしおください。',
+ beforeDate: '{label}以前の日付にしおください。',
+ startMonth: '開始月を遞択しおください。',
+ sameMonth: '日付が同䞀です。どちらかを倉曎しおください。'
+
+});
+
+/*
+---
+
+name: Locale.ja-JP.Number
+
+description: Number messages for Japanese.
+
+license: MIT-style license
+
+authors:
+ - Noritaka Horio
+
+requires:
+ - Locale
+
+provides: [Locale.ja-JP.Number]
+
+...
+*/
+
+Locale.define('ja-JP', 'Number', {
+
+ decimal: '.',
+ group: ',',
+
+ currency: {
+ decimals: 0,
+ prefix: '\\'
+ }
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Date
+
+description: Date messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Date]
+
+...
+*/
+
+Locale.define('nl-NL', 'Date', {
+
+ months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
+ days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'e',
+
+ lessThanMinuteAgo: 'minder dan een minuut geleden',
+ minuteAgo: 'ongeveer een minuut geleden',
+ minutesAgo: '{delta} minuten geleden',
+ hourAgo: 'ongeveer een uur geleden',
+ hoursAgo: 'ongeveer {delta} uur geleden',
+ dayAgo: 'een dag geleden',
+ daysAgo: '{delta} dagen geleden',
+ weekAgo: 'een week geleden',
+ weeksAgo: '{delta} weken geleden',
+ monthAgo: 'een maand geleden',
+ monthsAgo: '{delta} maanden geleden',
+ yearAgo: 'een jaar geleden',
+ yearsAgo: '{delta} jaar geleden',
+
+ lessThanMinuteUntil: 'over minder dan een minuut',
+ minuteUntil: 'over ongeveer een minuut',
+ minutesUntil: 'over {delta} minuten',
+ hourUntil: 'over ongeveer een uur',
+ hoursUntil: 'over {delta} uur',
+ dayUntil: 'over ongeveer een dag',
+ daysUntil: 'over {delta} dagen',
+ weekUntil: 'over een week',
+ weeksUntil: 'over {delta} weken',
+ monthUntil: 'over een maand',
+ monthsUntil: 'over {delta} maanden',
+ yearUntil: 'over een jaar',
+ yearsUntil: 'over {delta} jaar'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Form.Validator
+
+description: Form Validator messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+ - Arian Stolwijk
+ - Tim Wienk
+
+requires:
+ - Locale
+
+provides: [Locale.nl-NL.Form.Validator]
+
+...
+*/
+
+Locale.define('nl-NL', 'FormValidator', {
+
+ required: 'Dit veld is verplicht.',
+ length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).',
+ minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+ maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+ integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.',
+ numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").',
+ digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).',
+ alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+ alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.',
+ dateSuchAs: 'Vul een geldige datum in, zoals {date}',
+ dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+ email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".',
+ url: 'Vul een geldige URL in, zoals http://www.example.com.',
+ currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+ oneRequired: 'Vul iets in bij in ieder geval een van deze velden.',
+ warningPrefix: 'Waarschuwing: ',
+ errorPrefix: 'Fout: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Spaties zijn niet toegestaan in dit veld.',
+ reqChkByNode: 'Er zijn geen items geselecteerd.',
+ requiredChk: 'Dit veld is verplicht.',
+ reqChkByName: 'Selecteer een {label}.',
+ match: 'Dit veld moet overeen komen met het {matchName} veld',
+ startDate: 'de begin datum',
+ endDate: 'de eind datum',
+ currentDate: 'de huidige datum',
+ afterDate: 'De datum moet hetzelfde of na {label} zijn.',
+ beforeDate: 'De datum moet hetzelfde of voor {label} zijn.',
+ startMonth: 'Selecteer een begin maand',
+ sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.',
+ creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.'
+
+});
+
+/*
+---
+
+name: Locale.nl-NL.Number
+
+description: Number messages for Dutch.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.nl-NL.Number]
+
+...
+*/
+
+Locale.define('nl-NL').inherit('EU', 'Number');
+
+
+
+
+/*
+---
+
+name: Locale.no-NO.Date
+
+description: Date messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+ - Ole TÞsse Kolvik
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Date]
+
+...
+*/
+
+Locale.define('no-NO', 'Date', {
+ months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
+ months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
+ days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'],
+ days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ lessThanMinuteAgo: 'mindre enn et minutt siden',
+ minuteAgo: 'omtrent et minutt siden',
+ minutesAgo: '{delta} minutter siden',
+ hourAgo: 'omtrent en time siden',
+ hoursAgo: 'omtrent {delta} timer siden',
+ dayAgo: '{delta} dag siden',
+ daysAgo: '{delta} dager siden',
+ weekAgo: 'en uke siden',
+ weeksAgo: '{delta} uker siden',
+ monthAgo: 'en måned siden',
+ monthsAgo: '{delta} måneder siden',
+ yearAgo: 'ett år siden',
+ yearsAgo: '{delta} år siden',
+
+ lessThanMinuteUntil: 'mindre enn et minutt til',
+ minuteUntil: 'omtrent et minutt til',
+ minutesUntil: '{delta} minutter til',
+ hourUntil: 'omtrent en time til',
+ hoursUntil: 'omtrent {delta} timer til',
+ dayUntil: 'en dag til',
+ daysUntil: '{delta} dager til',
+ weekUntil: 'en uke til',
+ weeksUntil: '{delta} uker til',
+ monthUntil: 'en måned til',
+ monthsUntil: '{delta} måneder til',
+ yearUntil: 'et år til',
+ yearsUntil: '{delta} år til'
+});
+
+/*
+---
+
+name: Locale.no-NO.Form.Validator
+
+description: Form Validator messages for Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - Locale
+
+provides: [Locale.no-NO.Form.Validator]
+
+...
+*/
+
+Locale.define('no-NO', 'FormValidator', {
+
+ required: 'Dette feltet er påkrevd.',
+ minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+ maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+ integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+ numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+ digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+ alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+ dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}',
+ dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+ email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".',
+ url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.',
+ currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .',
+ oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.',
+ errorPrefix: 'Feil: ',
+ warningPrefix: 'Advarsel: '
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Date
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Date]
+
+...
+*/
+
+Locale.define('pl-PL', 'Date', {
+
+ months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+ months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'],
+ days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+ days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: 'nad ranem',
+ PM: 'po południu',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: function(dayOfMonth){
+ return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+ },
+
+ lessThanMinuteAgo: 'mniej niŌ minute temu',
+ minuteAgo: 'około minutę temu',
+ minutesAgo: '{delta} minut temu',
+ hourAgo: 'około godzinę temu',
+ hoursAgo: 'około {delta} godzin temu',
+ dayAgo: 'Wczoraj',
+ daysAgo: '{delta} dni temu',
+
+ lessThanMinuteUntil: 'za niecałą minutę',
+ minuteUntil: 'za około minutę',
+ minutesUntil: 'za {delta} minut',
+ hourUntil: 'za około godzinę',
+ hoursUntil: 'za około {delta} godzin',
+ dayUntil: 'za 1 dzień',
+ daysUntil: 'za {delta} dni'
+
+});
+
+/*
+---
+
+name: Locale.pl-PL.Form.Validator
+
+description: Form Validator messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - Locale
+
+provides: [Locale.pl-PL.Form.Validator]
+
+...
+*/
+
+Locale.define('pl-PL', 'FormValidator', {
+
+ required: 'To pole jest wymagane.',
+ minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+ maxLength: 'Dozwolone jest nie więcej niÅŒ {maxLength} znaków (wpisanych zostało {length})',
+ integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+ numeric: 'Prosimy uÅŒywać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+ digits: 'Prosimy uÅŒywać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+ alpha: 'Prosimy uÅŒywać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ alphanum: 'Prosimy uÅŒywać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+ dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}',
+ dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+ email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".',
+ url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.',
+ currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+ oneRequired: 'Prosimy wypełnić chociaÅŒ jedno z pól.',
+ errorPrefix: 'Błąd: ',
+ warningPrefix: 'Uwaga: ',
+
+ // Form.Validator.Extras
+ noSpace: 'W tym polu nie mogą znajdować się spacje.',
+ reqChkByNode: 'Brak zaznaczonych elementów.',
+ requiredChk: 'To pole jest wymagane.',
+ reqChkByName: 'Prosimy wybrać z {label}.',
+ match: 'To pole musi być takie samo jak {matchName}',
+ startDate: 'data początkowa',
+ endDate: 'data końcowa',
+ currentDate: 'aktualna data',
+ afterDate: 'Podana data poinna być taka sama lub po {label}.',
+ beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+ startMonth: 'Prosimy wybrać początkowy miesiąc.',
+ sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});
+
+/*
+---
+
+name: Locale.pt-PT.Date
+
+description: Date messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Date]
+
+...
+*/
+
+Locale.define('pt-PT', 'Date', {
+
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+ months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
+ days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+ days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
+
+ // Culture's date order: DD-MM-YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d-%m-%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: 'º',
+
+ lessThanMinuteAgo: 'há menos de um minuto',
+ minuteAgo: 'há cerca de um minuto',
+ minutesAgo: 'há {delta} minutos',
+ hourAgo: 'há cerca de uma hora',
+ hoursAgo: 'há cerca de {delta} horas',
+ dayAgo: 'há um dia',
+ daysAgo: 'há {delta} dias',
+ weekAgo: 'há uma semana',
+ weeksAgo: 'há {delta} semanas',
+ monthAgo: 'há um mês',
+ monthsAgo: 'há {delta} meses',
+ yearAgo: 'há um ano',
+ yearsAgo: 'há {delta} anos',
+
+ lessThanMinuteUntil: 'em menos de um minuto',
+ minuteUntil: 'em um minuto',
+ minutesUntil: 'em {delta} minutos',
+ hourUntil: 'em uma hora',
+ hoursUntil: 'em {delta} horas',
+ dayUntil: 'em um dia',
+ daysUntil: 'em {delta} dias',
+ weekUntil: 'em uma semana',
+ weeksUntil: 'em {delta} semanas',
+ monthUntil: 'em um mês',
+ monthsUntil: 'em {delta} meses',
+ yearUntil: 'em um ano',
+ yearsUntil: 'em {delta} anos'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Date
+
+description: Date messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - Locale
+ - Locale.pt-PT.Date
+
+provides: [Locale.pt-BR.Date]
+
+...
+*/
+
+Locale.define('pt-BR', 'Date', {
+
+ // Culture's date order: DD/MM/YYYY
+ shortDate: '%d/%m/%Y'
+
+}).inherit('pt-PT', 'Date');
+
+/*
+---
+
+name: Locale.pt-BR.Form.Validator
+
+description: Form Validator messages for Portuguese (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-BR', 'FormValidator', {
+
+ required: 'Este campo é obrigatório.',
+ minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+ maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+ integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+ numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+ alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".',
+ url: 'Digite uma URL válida. Exemplo: http://www.example.com.',
+ currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+ oneRequired: 'Digite algo para pelo menos um desses campos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Não é possível digitar espaços neste campo.',
+ reqChkByNode: 'Não foi selecionado nenhum item.',
+ requiredChk: 'Este campo é obrigatório.',
+ reqChkByName: 'Por favor digite um {label}.',
+ match: 'Este campo deve ser igual ao campo {matchName}.',
+ startDate: 'a data inicial',
+ endDate: 'a data final',
+ currentDate: 'a data atual',
+ afterDate: 'A data deve ser igual ou posterior a {label}.',
+ beforeDate: 'A data deve ser igual ou anterior a {label}.',
+ startMonth: 'Por favor selecione uma data inicial.',
+ sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+ creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});
+
+/*
+---
+
+name: Locale.pt-BR.Number
+
+description: Number messages for PT Brazilian.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Danillo César
+
+requires:
+ - Locale
+
+provides: [Locale.pt-BR.Number]
+
+...
+*/
+
+Locale.define('pt-BR', 'Number', {
+
+ decimal: ',',
+ group: '.',
+
+ currency: {
+ prefix: 'R$ '
+ }
+
+});
+
+
+
+/*
+---
+
+name: Locale.pt-PT.Form.Validator
+
+description: Form Validator messages for Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - Locale
+
+provides: [Locale.pt-PT.Form.Validator]
+
+...
+*/
+
+Locale.define('pt-PT', 'FormValidator', {
+
+ required: 'Este campo é necessário.',
+ minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+ maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+ integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+ numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+ digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+ alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+ alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+ dateSuchAs: 'Digite uma data válida, como {date}',
+ dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+ email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".',
+ url: 'Digite uma URL válida, como http://www.example.com.',
+ currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ',
+ oneRequired: 'Digite algo para pelo menos um desses insumos.',
+ errorPrefix: 'Erro: ',
+ warningPrefix: 'Aviso: '
+
+});
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Date
+
+description: Date messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Evstigneev Pavel
+ - Kuryanovich Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Date]
+
+...
+*/
+
+(function(){
+
+// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+// one -> n mod 10 is 1 and n mod 100 is not 11;
+// few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+// other -> everything else (example 3.14)
+var pluralize = function (n, one, few, many, other){
+ var modulo10 = n % 10,
+ modulo100 = n % 100;
+
+ if (modulo10 == 1 && modulo100 != 11){
+ return one;
+ } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return few;
+ } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){
+ return many;
+ } else {
+ return other;
+ }
+};
+
+Locale.define('ru-RU', 'Date', {
+
+ months: ['ЯМварь', 'Ѐевраль', 'Март', 'Апрель', 'Май', 'ИюМь', 'Июль', 'Август', 'СеМтябрь', 'Октябрь', 'НПябрь', 'Декабрь'],
+ months_abbr: ['яМв', 'февр', 'Ќарт', 'апр', 'Ќай','ОюМь','Оюль','авг','сеМт','Пкт','МПяб','Ўек'],
+ days: ['ВПскресеМье', 'ППМеЎельМОк', 'ВтПрМОк', 'СреЎа', 'Четверг', 'ПятМОца', 'СуббПта'],
+ days_abbr: ['Вс', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше ЌОМуты МазаЎ',
+ minuteAgo: 'ЌОМуту МазаЎ',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ' МазаЎ'; },
+ hourAgo: 'час МазаЎ',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ' МазаЎ'; },
+ dayAgo: 'вчера',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ' МазаЎ'; },
+ weekAgo: 'МеЎелю МазаЎ',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎеля', 'МеЎелО', 'МеЎель') + ' МазаЎ'; },
+ monthAgo: 'Ќесяц МазаЎ',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ' МазаЎ'; },
+ yearAgo: 'гПЎ МазаЎ',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ' МазаЎ'; },
+
+ lessThanMinuteUntil: 'ЌеМьше чеЌ через ЌОМуту',
+ minuteUntil: 'через ЌОМуту',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЌОМуту', 'ЌОМуты', 'ЌОМут') + ''; },
+ hourUntil: 'через час',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часПв') + ''; },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМей') + ''; },
+ weekUntil: 'через МеЎелю',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'МеЎелю', 'МеЎелО', 'МеЎель') + ''; },
+ monthUntil: 'через Ќесяц',
+ monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќесяц', 'Ќесяца', 'Ќесяцев') + ''; },
+ yearUntil: 'через',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'лет') + ''; }
+
+});
+
+
+
+})();
+
+/*
+---
+
+name: Locale.ru-RU-unicode.Form.Validator
+
+description: Form Validator messages for Russian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - Locale
+
+provides: [Locale.ru-RU.Form.Validator]
+
+...
+*/
+
+Locale.define('ru-RU', 'FormValidator', {
+
+ required: 'ЭтП пПле ПбязательМП к запПлМеМОю.',
+ minLength: 'ППжалуйста, ввеЎОте хПтя бы {minLength} сОЌвПлПв (Вы ввелО {length}).',
+ maxLength: 'ППжалуйста, ввеЎОте Ме бПльше {maxLength} сОЌвПлПв (Вы ввелО {length}).',
+ integer: 'ППжалуйста, ввеЎОте в этП пПле чОслП. ДрПбМые чОсла (МапрОЌер 1.25) тут Ме разрешеМы.',
+ numeric: 'ППжалуйста, ввеЎОте в этП пПле чОслП (МапрОЌер "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").',
+ digits: 'В этПЌ пПле Вы ЌПжете ОспПльзПвать тПлькП цОфры О зМакО пуМктуацОО (МапрОЌер, телефПММый МПЌер сП зМакаЌО ЎефОса ОлО с тПчкаЌО).',
+ alpha: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ alphanum: 'В этПЌ пПле ЌПжМП ОспПльзПвать тПлькП латОМскОе буквы (a-z) О цОфры (0-9). ПрПбелы О ЎругОе сОЌвПлы запрещеМы.',
+ dateSuchAs: 'ППжалуйста, ввеЎОте кПрректМую Ўату {date}',
+ dateInFormatMDY: 'ППжалуйста, ввеЎОте Ўату в фПрЌате ММ/ДД/ГГГГ (МапрОЌер "12/31/1999")',
+ email: 'ППжалуйста, ввеЎОте кПрректМый еЌейл-аЎрес. Для прОЌера "fred@domain.com".',
+ url: 'ППжалуйста, ввеЎОте правОльМую ссылку вОЎа http://www.example.com.',
+ currencyDollar: 'ППжалуйста, ввеЎОте суЌЌу в ЎПлларах. НапрОЌер: $100.00 .',
+ oneRequired: 'ППжалуйста, выберОте хПть чтП-МОбуЎь в ПЎМПЌ Оз этОх пПлей.',
+ errorPrefix: 'ОшОбка: ',
+ warningPrefix: 'ВМОЌаМОе: '
+
+});
+
+
+
+/*
+---
+
+name: Locale.sk-SK.Date
+
+description: Date messages for Slovak.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Date]
+
+...
+*/
+(function(){
+
+// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+// one -> n is 1; 1
+// few -> n in 2..4; 2-4
+// other -> everything else 0, 5-999, 1.31, 2.31, 5.31...
+var pluralize = function (n, one, few, other){
+ if (n == 1) return one;
+ else if (n == 2 || n == 3 || n == 4) return few;
+ else return other;
+};
+
+Locale.define('sk-SK', 'Date', {
+
+ months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
+ months_abbr: ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'],
+ days: ['Nedele', 'Pondelí', 'ÚterÜ', 'Streda', 'Čtvrtek', 'Pátek', 'Sobota'],
+ days_abbr: ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H:%M',
+ AM: 'dop.',
+ PM: 'pop.',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'pred chvíğou',
+ minuteAgo: 'priblişne pred minútou',
+ minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); },
+ hourAgo: 'pribliÅŸne pred hodinou',
+ hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); },
+ dayAgo: 'pred dňom',
+ daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dňom', 'dňami', 'dňami'); },
+ weekAgo: 'pred tÜşdňom',
+ weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdňom', 'tÜşdňami', 'tÜşdňami'); },
+ monthAgo: 'pred mesiacom',
+ monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); },
+ yearAgo: 'pred rokom',
+ yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); },
+
+ lessThanMinuteUntil: 'o chvíğu',
+ minuteUntil: 'priblişne o minútu',
+ minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); },
+ hourUntil: 'pribliÅŸne o hodinu',
+ hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodín'); },
+ dayUntil: 'o deň',
+ daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deň', 'dni', 'dní'); },
+ weekUntil: 'o tÜşdeň',
+ weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeň', 'tÜşdne', 'tÜşdňov'); },
+ monthUntil: 'o mesiac',
+ monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); },
+ yearUntil: 'o rok',
+ yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); }
+});
+
+})();
+
+/*
+---
+
+name: Locale.sk-SK.Form.Validator
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Ivan Masár
+
+requires:
+ - Locale
+
+provides: [Locale.sk-SK.Form.Validator]
+
+...
+*/
+
+Locale.define('sk-SK', 'FormValidator', {
+
+ required: 'Táto poloşka je povinná.',
+ minLength: 'Zadajte prosím aspoň {minLength} znakov (momentálne {length} znakov).',
+ maxLength: 'Zadajte prosím menej ako {maxLength} znakov (momentálne {length} znakov).',
+ integer: 'Zadajte prosím celé číslo. Desetinné čísla (napr. 1.25) nie sú povolené.',
+ numeric: 'Zadajte len číselné hodnoty (t.j. „1“ alebo „1.1“ alebo „-1“ alebo „-1.1“).',
+ digits: 'Zadajte prosím len čísla a interpunkčné znamienka (napríklad telefónne číslo s pomlčkami albo bodkami je povolené).',
+ alpha: 'Zadajte prosím len písmená (a-z). Medzery alebo iné znaky nie sú povolené.',
+ alphanum: 'Zadajte prosím len písmená (a-z) alebo číslice (0-9). Medzery alebo iné znaky nie sú povolené.',
+ dateSuchAs: 'Zadajte prosím platnÜ dátum v tvare {date}',
+ dateInFormatMDY: 'Zadajte prosím platnÜ datum v tvare MM / DD / RRRR (t.j. „12/31/1999“)',
+ email: 'Zadajte prosím platnú emailovú adresu. Napríklad „fred@domain.com“.',
+ url: 'Zadajte prosím platnoú adresu URL v tvare http://www.example.com.',
+ currencyDollar: 'Zadajte prosím platnú čiastku. Napríklad $100.00.',
+ oneRequired: 'Zadajte prosím aspoň jednu hodnotu z tÜchto poloÅŸiek.',
+ errorPrefix: 'Chyba: ',
+ warningPrefix: 'Upozornenie: ',
+
+ // Form.Validator.Extras
+ noSpace: 'V tejto poloşle nie sú povolené medzery',
+ reqChkByNode: 'Nie sú vybrané şiadne poloşky.',
+ requiredChk: 'Táto poloşka je povinná.',
+ reqChkByName: 'Prosím vyberte {label}.',
+ match: 'Táto poloşka sa musí zhodovať s poloşkou {matchName}',
+ startDate: 'dátum začiatku',
+ endDate: 'dátum ukončenia',
+ currendDate: 'aktuálny dátum',
+ afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀčší ako {label}.',
+ beforeDate: 'Dátum by mal byť rovnakÜ alebo menší ako {label}.',
+ startMonth: 'Vyberte počiatočnÜ mesiac.',
+ sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeňte jeden z nich.',
+ creditcard: 'Zadané číslo kreditnej karty je neplatné. Prosím, opravte ho. Bolo zadanÜch {length} číslic.'
+
+});
+
+/*
+---
+
+name: Locale.si-SI.Date
+
+description: Date messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, two, three, other){
+ return (n >= 1 && n <= 3) ? arguments[n] : other;
+};
+
+Locale.define('sl-SI', 'Date', {
+
+ months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'],
+ days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'],
+ days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'],
+
+ // Culture's date order: DD.MM.YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d.%m.%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '.',
+
+ lessThanMinuteAgo: 'manj kot minuto nazaj',
+ minuteAgo: 'minuto nazaj',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; },
+ hourAgo: 'uro nazaj',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; },
+ dayAgo: 'dan nazaj',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; },
+ weekAgo: 'teden nazaj',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; },
+ monthAgo: 'mesec nazaj',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; },
+ yearthAgo: 'leto nazaj',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; },
+
+ lessThanMinuteUntil: 'še manj kot minuto',
+ minuteUntil: 'še minuta',
+ minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); },
+ hourUntil: 'še ura',
+ hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); },
+ dayUntil: 'še dan',
+ daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); },
+ weekUntil: 'še tedn',
+ weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); },
+ monthUntil: 'še mesec',
+ monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); },
+ yearUntil: 'še leto',
+ yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.si-SI.Form.Validator
+
+description: Form Validator messages for Slovenian.
+
+license: MIT-style license
+
+authors:
+ - Radovan Lozej
+
+requires:
+ - Locale
+
+provides: [Locale.si-SI.Form.Validator]
+
+...
+*/
+
+Locale.define('sl-SI', 'FormValidator', {
+
+ required: 'To polje je obvezno',
+ minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).',
+ maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).',
+ integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.',
+ numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").',
+ digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).',
+ alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.',
+ alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.',
+ dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}',
+ dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")',
+ email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".',
+ url: 'Prosim, vnesite pravilen URL kot http://www.example.com.',
+ currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .',
+ oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.',
+ errorPrefix: 'Napaka: ',
+ warningPrefix: 'Opozorilo: ',
+
+ // Form.Validator.Extras
+ noSpace: 'To vnosno polje ne dopušča presledkov.',
+ reqChkByNode: 'Nič niste izbrali.',
+ requiredChk: 'To polje je obvezno',
+ reqChkByName: 'Prosim, izberite {label}.',
+ match: 'To polje se mora ujemati z poljem {matchName}',
+ startDate: 'datum začetka',
+ endDate: 'datum konca',
+ currentDate: 'trenuten datum',
+ afterDate: 'Datum bi moral biti isti ali po {label}.',
+ beforeDate: 'Datum bi moral biti isti ali pred {label}.',
+ startMonth: 'Prosim, vnesite začetni datum',
+ sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.',
+ creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Date
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Date]
+
+...
+*/
+
+Locale.define('sv-SE', 'Date', {
+
+ months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+ months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
+ days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+ days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%H:%M',
+ AM: '',
+ PM: '',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'mindre Àn en minut sedan',
+ minuteAgo: 'ungefÀr en minut sedan',
+ minutesAgo: '{delta} minuter sedan',
+ hourAgo: 'ungefÀr en timme sedan',
+ hoursAgo: 'ungefÀr {delta} timmar sedan',
+ dayAgo: '1 dag sedan',
+ daysAgo: '{delta} dagar sedan',
+
+ lessThanMinuteUntil: 'mindre Àn en minut sedan',
+ minuteUntil: 'ungefÀr en minut sedan',
+ minutesUntil: '{delta} minuter sedan',
+ hourUntil: 'ungefÀr en timme sedan',
+ hoursUntil: 'ungefÀr {delta} timmar sedan',
+ dayUntil: '1 dag sedan',
+ daysUntil: '{delta} dagar sedan'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Form.Validator
+
+description: Form Validator messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - Locale
+
+provides: [Locale.sv-SE.Form.Validator]
+
+...
+*/
+
+Locale.define('sv-SE', 'FormValidator', {
+
+ required: 'FÀltet Àr obligatoriskt.',
+ minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).',
+ maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+ integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillåtna.',
+ numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+ digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillåtet).',
+ alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillåtna.',
+ dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}',
+ dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+ email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".',
+ url: 'Ange en giltig webbadress som http://www.example.com.',
+ currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.',
+ oneRequired: 'VÀnligen ange minst ett av dessa alternativ.',
+ errorPrefix: 'Fel: ',
+ warningPrefix: 'Varning: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Det får inte finnas några mellanslag i detta fÀlt.',
+ reqChkByNode: 'Inga objekt Àr valda.',
+ requiredChk: 'Detta Àr ett obligatoriskt fÀlt.',
+ reqChkByName: 'VÀlj en {label}.',
+ match: 'Detta fÀlt måste matcha {matchName}',
+ startDate: 'startdatumet',
+ endDate: 'slutdatum',
+ currentDate: 'dagens datum',
+ afterDate: 'Datumet bör vara samma eller senare Àn {label}.',
+ beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.',
+ startMonth: 'VÀlj en start månad',
+ sameMonth: 'Dessa två datum måste vara i samma månad - du måste Àndra det ena eller det andra.'
+
+});
+
+/*
+---
+
+name: Locale.sv-SE.Number
+
+description: Number messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+ - Martin Lundgren
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.sv-SE.Number]
+
+...
+*/
+
+Locale.define('sv-SE', 'Number', {
+
+ currency: {
+ prefix: 'SEK '
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.tr-TR.Date
+
+description: Date messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Date]
+
+...
+*/
+
+Locale.define('tr-TR', 'Date', {
+
+ months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'],
+ months_abbr: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
+ days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
+ days_abbr: ['Pa', 'Pzt', 'Sa', 'Ça', 'Pe', 'Cu', 'Cmt'],
+
+ // Culture's date order: MM/DD/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H.%M',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'bir dakikadan önce',
+ minuteAgo: 'yaklaşık bir dakika önce',
+ minutesAgo: '{delta} dakika önce',
+ hourAgo: 'bir saat kadar önce',
+ hoursAgo: '{delta} saat kadar önce',
+ dayAgo: 'bir gÌn önce',
+ daysAgo: '{delta} gÌn önce',
+ weekAgo: 'bir hafta önce',
+ weeksAgo: '{delta} hafta önce',
+ monthAgo: 'bir ay önce',
+ monthsAgo: '{delta} ay önce',
+ yearAgo: 'bir yıl önce',
+ yearsAgo: '{delta} yıl önce',
+
+ lessThanMinuteUntil: 'bir dakikadan az sonra',
+ minuteUntil: 'bir dakika kadar sonra',
+ minutesUntil: '{delta} dakika sonra',
+ hourUntil: 'bir saat kadar sonra',
+ hoursUntil: '{delta} saat kadar sonra',
+ dayUntil: 'bir gÃŒn sonra',
+ daysUntil: '{delta} gÃŒn sonra',
+ weekUntil: 'bir hafta sonra',
+ weeksUntil: '{delta} hafta sonra',
+ monthUntil: 'bir ay sonra',
+ monthsUntil: '{delta} ay sonra',
+ yearUntil: 'bir yıl sonra',
+ yearsUntil: '{delta} yıl sonra'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Form.Validator
+
+description: Form Validator messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+
+provides: [Locale.tr-TR.Form.Validator]
+
+...
+*/
+
+Locale.define('tr-TR', 'FormValidator', {
+
+ required: 'Bu alan zorunlu.',
+ minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).',
+ maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).',
+ integer: 'LÌtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.',
+ numeric: 'LÃŒtfen bu alana sadece sayısal değer girin (ör: "1", "1.1", "-1" ya da "-1.1").',
+ digits: 'LÃŒtfen bu alana sadece sayısal değer ve noktalama işareti girin (örneğin, nokta ve tire içeren bir telefon numarası kullanılabilir).',
+ alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. Boşluk ve diğer karakterler kullanılamaz.',
+ dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ör: {date})',
+ dateInFormatMDY: 'LÌtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")',
+ email: 'LÃŒtfen geçerli bir email adresi girin. Ör: "kemal@etikan.com".',
+ url: 'LÃŒtfen geçerli bir URL girin. Ör: http://www.example.com.',
+ currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ör: 100,00 TL .',
+ oneRequired: 'LÃŒtfen en az bir tanesini doldurun.',
+ errorPrefix: 'Hata: ',
+ warningPrefix: 'Uyarı: ',
+
+ // Form.Validator.Extras
+ noSpace: 'Bu alanda boşluk kullanılamaz.',
+ reqChkByNode: 'Hiçbir öğe seçilmemiş.',
+ requiredChk: 'Bu alan zorunlu.',
+ reqChkByName: 'LÃŒtfen bir {label} girin.',
+ match: 'Bu alan, {matchName} alanıyla uyuşmalı',
+ startDate: 'başlangıç tarihi',
+ endDate: 'bitiş tarihi',
+ currentDate: 'bugÃŒnÃŒn tarihi',
+ afterDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan sonra olmalıdır.',
+ beforeDate: 'Tarih, {label} tarihiyle aynı gÌn ya da ondan önce olmalıdır.',
+ startMonth: 'LÃŒtfen bir başlangıç ayı seçin',
+ sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini değiştirmeniz gerekiyor.',
+ creditcard: 'Girdiğiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.'
+
+});
+
+/*
+---
+
+name: Locale.tr-TR.Number
+
+description: Number messages for Turkish.
+
+license: MIT-style license
+
+authors:
+ - Faruk Can Bilir
+
+requires:
+ - Locale
+ - Locale.EU.Number
+
+provides: [Locale.tr-TR.Number]
+
+...
+*/
+
+Locale.define('tr-TR', 'Number', {
+
+ currency: {
+ decimals: 0,
+ suffix: ' TL'
+ }
+
+}).inherit('EU', 'Number');
+
+/*
+---
+
+name: Locale.uk-UA.Date
+
+description: Date messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Date]
+
+...
+*/
+
+(function(){
+
+var pluralize = function(n, one, few, many, other){
+ var d = (n / 10).toInt(),
+ z = n % 10,
+ s = (n / 100).toInt();
+
+ if (d == 1 && n > 10) return many;
+ if (z == 1) return one;
+ if (z > 0 && z < 5) return few;
+ return many;
+};
+
+Locale.define('uk-UA', 'Date', {
+
+ months: ['СічеМь', 'ЛютОй', 'БерезеМь', 'КвітеМь', 'ТравеМь', 'ЧервеМь', 'ЛОпеМь', 'СерпеМь', 'ВересеМь', 'ЖПвтеМь', 'ЛОстПпаЎ', 'ГруЎеМь'],
+ months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'ЛОп', 'Серп', 'Вер', 'ЖПвт', 'ЛОст', 'ГруЎ' ],
+ days: ['НеЎіля', 'ППМеЎілПк', 'ВівтПрПк', 'СереЎа', 'Четвер', "П'ятМОця", 'СубПта'],
+ days_abbr: ['НЎ', 'ПМ', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
+
+ // Culture's date order: DD/MM/YYYY
+ dateOrder: ['date', 'month', 'year'],
+ shortDate: '%d/%m/%Y',
+ shortTime: '%H:%M',
+ AM: 'ЎП пПлуЎМя',
+ PM: 'пП пПлуЎМю',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: 'ЌеМьше хвОлОМО тПЌу',
+ minuteAgo: 'хвОлОМу тПЌу',
+ minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ') + ' тПЌу'; },
+ hourAgo: 'гПЎОМу тПЌу',
+ hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ') + ' тПЌу'; },
+ dayAgo: 'вчПра',
+ daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів') + ' тПЌу'; },
+ weekAgo: 'тОжЎеМь тПЌу',
+ weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів') + ' тПЌу'; },
+ monthAgo: 'Ќісяць тПЌу',
+ monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців') + ' тПЌу'; },
+ yearAgo: 'рік тПЌу',
+ yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків') + ' тПЌу'; },
+
+ lessThanMinuteUntil: 'за ЌОть',
+ minuteUntil: 'через хвОлОМу',
+ minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвОлОМу', 'хвОлОМО', 'хвОлОМ'); },
+ hourUntil: 'через гПЎОМу',
+ hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'гПЎОМу', 'гПЎОМО', 'гПЎОМ'); },
+ dayUntil: 'завтра',
+ daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'ЎеМь', 'ЎМя', 'ЎМів'); },
+ weekUntil: 'через тОжЎеМь',
+ weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тОжЎеМь', 'тОжМі', 'тОжМів'); },
+ monthUntil: 'через Ќісяць',
+ monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'Ќісяць', 'Ќісяці', 'Ќісяців'); },
+ yearUntil: 'через рік',
+ yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'рПкО', 'рПків'); }
+
+});
+
+})();
+
+/*
+---
+
+name: Locale.uk-UA.Form.Validator
+
+description: Form Validator messages for Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - Locale
+
+provides: [Locale.uk-UA.Form.Validator]
+
+...
+*/
+
+Locale.define('uk-UA', 'FormValidator', {
+
+ required: 'Ње пПле пПвОММе бутО запПвМеМОЌ.',
+ minLength: 'ВвеЎіть хПча б {minLength} сОЌвПлів (ВО ввелО {length}).',
+ maxLength: 'Кількість сОЌвПлів Ме ЌПже бутО більше {maxLength} (ВО ввелО {length}).',
+ integer: 'ВвеЎіть в це пПле чОслП. ДрПбПві чОсла (МапрОклаЎ 1.25) Ме ЎПзвПлеМі.',
+ numeric: 'ВвеЎіть в це пПле чОслП (МапрОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").',
+ digits: 'В цьПЌу пПлі вО ЌПжете вОкПрОстПвуватО лОше цОфрО і зМакО пуМктіації (МапрОклаЎ, телефПММОй МПЌер з зМакаЌО Ўефізу абП з крапкаЌО).',
+ alpha: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ alphanum: 'В цьПЌу пПлі ЌПжМа вОкПрОстПвуватО лОше латОМські літерО (a-z) і цОфрО (0-9). ПрПбілО і іМші сОЌвПлО забПрПМеМі.',
+ dateSuchAs: 'ВвеЎіть кПректМу Ўату {date}.',
+ dateInFormatMDY: 'ВвеЎіть Ўату в фПрЌаті ММ/ДД/РРРР (МапрОклаЎ "12/31/2009").',
+ email: 'ВвеЎіть кПректМу аЎресу електрПММПї пПштО (МапрОклаЎ "name@domain.com").',
+ url: 'ВвеЎіть кПректМе іМтерМет-пПсОлаММя (МапрОклаЎ http://www.example.com).',
+ currencyDollar: 'ВвеЎіть суЌу в ЎПларах (МапрОклаЎ "$100.00").',
+ oneRequired: 'ЗапПвМіть ПЎМе з пПлів.',
+ errorPrefix: 'ППЌОлка: ',
+ warningPrefix: 'Увага: ',
+
+ noSpace: 'ПрПбілО забПрПМеМі.',
+ reqChkByNode: 'Не віЎЌічеМП жПЎМПгП варіаМту.',
+ requiredChk: 'Ње пПле пПвОММе бутО віЌічеМОЌ.',
+ reqChkByName: 'БуЎь ласка, віЎЌітьте {label}.',
+ match: 'Ње пПле пПвОММП віЎпПвіЎатО {matchName}',
+ startDate: 'пПчаткПва Ўата',
+ endDate: 'кіМцева Ўата',
+ currentDate: 'сьПгПЎМішМя Ўата',
+ afterDate: 'Њя Ўата пПвОММа бутО такПю ж, абП пізМішПю за {label}.',
+ beforeDate: 'Њя Ўата пПвОММа бутО такПю ж, абП раМішПю за {label}.',
+ startMonth: 'БуЎь ласка, вОберіть пПчаткПвОй Ќісяць',
+ sameMonth: 'Њі ЎатО пПвОММі віЎМПсОтОсь ПЎМПгП і тПгП ж Ќісяця. БуЎь ласка, зЌіМіть ПЎМу з МОх.',
+ creditcard: 'НПЌер креЎОтМПї картО ввеЎеМОй МеправОльМП. БуЎь ласка, перевірте йПгП. ВвеЎеМП {length} сОЌвПлів.'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Date
+
+description: Date messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+
+provides: [Locale.zh-CH.Date]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分钟前',
+ minuteAgo: '倧纊1分钟前',
+ minutesAgo: '{delta}分钟之前',
+ hourAgo: '倧纊1小时前',
+ hoursAgo: '倧纊{delta}小时前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '从现圚匀始䞍到1分钟',
+ minuteUntil: '从现圚匀始玄1分钟',
+ minutesUntil: '从现圚匀始纊{delta}分钟',
+ hourUntil: '从现圚匀始1小时',
+ hoursUntil: '从现圚匀始纊{delta}小时',
+ dayUntil: '从现圚匀始1倩',
+ daysUntil: '从现圚匀始{delta}倩',
+ weekUntil: '从现圚匀始1星期',
+ weeksUntil: '从现圚匀始{delta}星期',
+ monthUntil: '从现圚匀始䞀䞪月',
+ monthsUntil: '从现圚匀始{delta}䞪月',
+ yearUntil: '从现圚匀始1幎',
+ yearsUntil: '从现圚匀始{delta}幎'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'Date', {
+
+ months: ['䞀月', '二月', '䞉月', '四月', '五月', '六月', '䞃月', '八月', '九月', '十月', '十䞀月', '十二月'],
+ months_abbr: ['侀', '二', '侉', '四', '五', '六', '䞃', '八', '九', '十', '十䞀', '十二'],
+ days: ['星期日', '星期䞀', '星期二', '星期䞉', '星期四', '星期五', '星期六'],
+ days_abbr: ['日', '侀', '二', '侉', '四', '五', '六'],
+
+ // Culture's date order: YYYY-MM-DD
+ dateOrder: ['year', 'month', 'date'],
+ shortDate: '%Y-%m-%d',
+ shortTime: '%I:%M%p',
+ AM: 'AM',
+ PM: 'PM',
+ firstDayOfWeek: 1,
+
+ // Date.Extras
+ ordinal: '',
+
+ lessThanMinuteAgo: '䞍到1分鐘前',
+ minuteAgo: '倧玄1分鐘前',
+ minutesAgo: '{delta}分鐘之前',
+ hourAgo: '倧玄1小時前',
+ hoursAgo: '倧玄{delta}小時前',
+ dayAgo: '1倩前',
+ daysAgo: '{delta}倩前',
+ weekAgo: '1星期前',
+ weeksAgo: '{delta}星期前',
+ monthAgo: '1䞪月前',
+ monthsAgo: '{delta}䞪月前',
+ yearAgo: '1幎前',
+ yearsAgo: '{delta}幎前',
+
+ lessThanMinuteUntil: '埞珟圚開始䞍到1分鐘',
+ minuteUntil: '埞珟圚開始玄1分鐘',
+ minutesUntil: '埞珟圚開始玄{delta}分鐘',
+ hourUntil: '埞珟圚開始1小時',
+ hoursUntil: '埞珟圚開始玄{delta}小時',
+ dayUntil: '埞珟圚開始1倩',
+ daysUntil: '埞珟圚開始{delta}倩',
+ weekUntil: '埞珟圚開始1星期',
+ weeksUntil: '埞珟圚開始{delta}星期',
+ monthUntil: '埞珟圚開始䞀個月',
+ monthsUntil: '埞珟圚開始{delta}個月',
+ yearUntil: '埞珟圚開始1幎',
+ yearsUntil: '埞珟圚開始{delta}幎'
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Form.Validator
+
+description: Form Validator messages for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Form.Validator
+
+provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'FormValidator', {
+
+ required: '歀项必填。',
+ minLength: '请至少蟓入 {minLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ maxLength: '最倚只胜蟓入 {maxLength} 䞪字笊 (已蟓入 {length} 䞪)。',
+ integer: '请蟓入䞀䞪敎数䞍胜包含小数点。䟋劂"1", "200"。',
+ numeric: '请蟓入䞀䞪数字䟋劂"1", "1.1", "-1", "-1.1"。',
+ digits: '请蟓入由数字和标点笊号组成的内容。䟋劂电话号码。',
+ alpha: '请蟓入 A-Z 的 26 䞪字母䞍胜包含空栌或任䜕其他字笊。',
+ alphanum: '请蟓入 A-Z 的 26 䞪字母或 0-9 的 10 䞪数字䞍胜包含空栌或任䜕其他字笊。',
+ dateSuchAs: '请蟓入合法的日期栌匏劂{date}。',
+ dateInFormatMDY: '请蟓入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。',
+ email: '请蟓入合法的电子信箱地址䟋劂"fred@domain.com"。',
+ url: '请蟓入合法的 Url 地址䟋劂http://www.example.com。',
+ currencyDollar: '请蟓入合法的莧垁笊号䟋劂¥100.0',
+ oneRequired: '请至少选择䞀项。',
+ errorPrefix: '错误',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。',
+ reqChkByNode: '未选择任䜕内容。',
+ requiredChk: '歀项必填。',
+ reqChkByName: '请选择 {label}.',
+ match: '必须䞎{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '结束日期',
+ currentDate: '圓前日期',
+ afterDate: '日期必须等于或晚于 {label}.',
+ beforeDate: '日期必须早于或等于 {label}.',
+ startMonth: '请选择起始月仜',
+ sameMonth: '悚必须修改䞀䞪日期䞭的䞀䞪以确保它们圚同䞀月仜。',
+ creditcard: '悚蟓入的信甚卡号码䞍正确。圓前已蟓入{length}䞪字笊。'
+
+});
+
+// Traditional Chinese
+Locale.define('zh-CHT', 'FormValidator', {
+
+ required: '歀項必填。 ',
+ minLength: '請至少茞入{minLength} 個字笊(已茞入{length} 個)。 ',
+ maxLength: '最倚只胜茞入{maxLength} 個字笊(已茞入{length} 個)。 ',
+ integer: '請茞入䞀個敎敞䞍胜包含小敞點。䟋劂"1", "200"。 ',
+ numeric: '請茞入䞀個敞字䟋劂"1", "1.1", "-1", "-1.1"。 ',
+ digits: '請茞入由敞字和暙點笊號組成的內容。䟋劂電話號碌。 ',
+ alpha: '請茞入AZ 的26 個字母䞍胜包含空栌或任䜕其他字笊。 ',
+ alphanum: '請茞入AZ 的26 個字母或0-9 的10 個敞字䞍胜包含空栌或任䜕其他字笊。 ',
+ dateSuchAs: '請茞入合法的日期栌匏劂{date}。 ',
+ dateInFormatMDY: '請茞入合法的日期栌匏䟋劂YYYY-MM-DD ("2010-12-31")。 ',
+ email: '請茞入合法的電子信箱地址䟋劂"fred@domain.com"。 ',
+ url: '請茞入合法的Url 地址䟋劂http://www.example.com。 ',
+ currencyDollar: '請茞入合法的貚幣笊號䟋劂¥100.0',
+ oneRequired: '請至少遞擇䞀項。 ',
+ errorPrefix: '錯誀',
+ warningPrefix: '譊告',
+
+ // Form.Validator.Extras
+ noSpace: '䞍胜包含空栌。 ',
+ reqChkByNode: '未遞擇任䜕內容。 ',
+ requiredChk: '歀項必填。 ',
+ reqChkByName: '請遞擇 {label}.',
+ match: '必須與{matchName}盞匹配',
+ startDate: '起始日期',
+ endDate: '結束日期',
+ currentDate: '當前日期',
+ afterDate: '日期必須等斌或晚斌{label}.',
+ beforeDate: '日期必須早斌或等斌{label}.',
+ startMonth: '請遞擇起始月仜',
+ sameMonth: '悚必須修改兩個日期䞭的䞀個以確保它們圚同䞀月仜。 ',
+ creditcard: '悚茞入的信甚卡號碌䞍正確。當前已茞入{length}個字笊。 '
+
+});
+
+Form.Validator.add('validate-currency-yuan', {
+
+ errorMsg: function(){
+ return Form.Validator.getMsg('currencyYuan');
+ },
+
+ test: function(element){
+ // [ï¿¥]1[##][,###]+[.##]
+ // [ï¿¥]1###+[.##]
+ // [ï¿¥]0.##
+ // [ï¿¥].##
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+ }
+
+});
+
+/*
+---
+
+name: Locale.zh-CH.Number
+
+description: Number messages for for Chinese (simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - YMind Chan
+
+requires:
+ - Locale
+ - Locale.en-US.Number
+
+provides: [Locale.zh-CH.Number]
+
+...
+*/
+
+// Simplified Chinese
+Locale.define('zh-CHS', 'Number', {
+
+ currency: {
+ prefix: 'ï¿¥ '
+ }
+
+}).inherit('en-US', 'Number');
+
+// Traditional Chinese
+Locale.define('zh-CHT').inherit('zh-CHS', 'Number');
+
+/*
+---
+
+script: Request.JSONP.js
+
+name: Request.JSONP
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+ - Arian Stolwijk
+
+requires:
+ - Core/Element
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(src, scriptElement){},
+ onComplete: function(data){},
+ onSuccess: function(data){},
+ onCancel: function(){},
+ onTimeout: function(){},
+ onError: function(){}, */
+ onRequest: function(src){
+ if (this.options.log && window.console && console.log){
+ console.log('JSONP retrieving script with url:' + src);
+ }
+ },
+ onError: function(src){
+ if (this.options.log && window.console && console.warn){
+ console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+ }
+ },
+ url: '',
+ callbackKey: 'callback',
+ injectScript: document.head,
+ data: '',
+ link: 'ignore',
+ timeout: 0,
+ log: false
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ send: function(options){
+ if (!Request.prototype.check.call(this, options)) return this;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+ options = Object.merge(this.options, options || {});
+
+ var data = options.data;
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ var index = this.index = Request.JSONP.counter++;
+
+ var src = options.url +
+ (options.url.test('\\?') ? '&' :'?') +
+ (options.callbackKey) +
+ '=Request.JSONP.request_map.request_'+ index +
+ (data ? '&' + data : '');
+
+ if (src.length > 2083) this.fireEvent('error', src);
+
+ Request.JSONP.request_map['request_' + index] = function(){
+ this.success(arguments, index);
+ }.bind(this);
+
+ var script = this.getScript(src).inject(options.injectScript);
+ this.fireEvent('request', [src, script]);
+
+ if (options.timeout) this.timeout.delay(options.timeout, this);
+
+ return this;
+ },
+
+ getScript: function(src){
+ if (!this.script) this.script = new Element('script', {
+ type: 'text/javascript',
+ async: true,
+ src: src
+ });
+ return this.script;
+ },
+
+ success: function(args, index){
+ if (!this.running) return;
+ this.clear()
+ .fireEvent('complete', args).fireEvent('success', args)
+ .callChain();
+ },
+
+ cancel: function(){
+ if (this.running) this.clear().fireEvent('cancel');
+ return this;
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ clear: function(){
+ this.running = false;
+ if (this.script){
+ this.script.destroy();
+ this.script = null;
+ }
+ return this;
+ },
+
+ timeout: function(){
+ if (this.running){
+ this.running = false;
+ this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
+ }
+ return this;
+ }
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};
+
+/*
+---
+
+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: Request.Queue.js
+
+name: Request.Queue
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element
+ - Core/Request
+ - Class.Binds
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+ options: {/*
+ onRequest: function(argsPassedToOnRequest){},
+ onSuccess: function(argsPassedToOnSuccess){},
+ onComplete: function(argsPassedToOnComplete){},
+ onCancel: function(argsPassedToOnCancel){},
+ onException: function(argsPassedToOnException){},
+ onFailure: function(argsPassedToOnFailure){},
+ onEnd: function(){},
+ */
+ stopOnFailure: true,
+ autoAdvance: true,
+ concurrent: 1,
+ requests: {}
+ },
+
+ initialize: function(options){
+ var requests;
+ if (options){
+ requests = options.requests;
+ delete options.requests;
+ }
+ this.setOptions(options);
+ this.requests = {};
+ this.queue = [];
+ this.reqBinders = {};
+
+ if (requests) this.addRequests(requests);
+ },
+
+ addRequest: function(name, request){
+ this.requests[name] = request;
+ this.attach(name, request);
+ return this;
+ },
+
+ addRequests: function(obj){
+ Object.each(obj, function(req, name){
+ this.addRequest(name, req);
+ }, this);
+ return this;
+ },
+
+ getName: function(req){
+ return Object.keyOf(this.requests, req);
+ },
+
+ attach: function(name, req){
+ if (req._groupSend) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ if (!this.reqBinders[name]) this.reqBinders[name] = {};
+ this.reqBinders[name][evt] = function(){
+ this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
+ }.bind(this);
+ req.addEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req._groupSend = req.send;
+ req.send = function(options){
+ this.send(name, options);
+ return req;
+ }.bind(this);
+ return this;
+ },
+
+ removeRequest: function(req){
+ var name = typeOf(req) == 'object' ? this.getName(req) : req;
+ if (!name && typeOf(name) != 'string') return this;
+ req = this.requests[name];
+ if (!req) return this;
+ ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+ req.removeEvent(evt, this.reqBinders[name][evt]);
+ }, this);
+ req.send = req._groupSend;
+ delete req._groupSend;
+ return this;
+ },
+
+ getRunning: function(){
+ return Object.filter(this.requests, function(r){
+ return r.running;
+ });
+ },
+
+ isRunning: function(){
+ return !!(Object.keys(this.getRunning()).length);
+ },
+
+ send: function(name, options){
+ var q = function(){
+ this.requests[name]._groupSend(options);
+ this.queue.erase(q);
+ }.bind(this);
+
+ q.name = name;
+ if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+ else q();
+ return this;
+ },
+
+ hasNext: function(name){
+ return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+ },
+
+ resume: function(){
+ this.error = false;
+ (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
+ return this;
+ },
+
+ runNext: function(name){
+ if (!this.queue.length) return this;
+ if (!name){
+ this.queue[0]();
+ } else {
+ var found;
+ this.queue.each(function(q){
+ if (!found && q.name == name){
+ found = true;
+ q();
+ }
+ });
+ }
+ return this;
+ },
+
+ runAll: function(){
+ this.queue.each(function(q){
+ q();
+ });
+ return this;
+ },
+
+ clear: function(name){
+ if (!name){
+ this.queue.empty();
+ } else {
+ this.queue = this.queue.map(function(q){
+ if (q.name != name) return q;
+ else return false;
+ }).filter(function(q){
+ return q;
+ });
+ }
+ return this;
+ },
+
+ cancel: function(name){
+ this.requests[name].cancel();
+ return this;
+ },
+
+ onRequest: function(){
+ this.fireEvent('request', arguments);
+ },
+
+ onComplete: function(){
+ this.fireEvent('complete', arguments);
+ if (!this.queue.length) this.fireEvent('end');
+ },
+
+ onCancel: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('cancel', arguments);
+ },
+
+ onSuccess: function(){
+ if (this.options.autoAdvance && !this.error) this.runNext();
+ this.fireEvent('success', arguments);
+ },
+
+ onFailure: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('failure', arguments);
+ },
+
+ onException: function(){
+ this.error = true;
+ if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+ this.fireEvent('exception', arguments);
+ }
+
+});
+
+/*
+---
+
+script: Array.Extras.js
+
+name: Array.Extras
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Sebastian Markbåge
+
+requires:
+ - Core/Array
+ - MooTools.More
+
+provides: [Array.Extras]
+
+...
+*/
+
+(function(nil){
+
+Array.implement({
+
+ min: function(){
+ return Math.min.apply(null, this);
+ },
+
+ max: function(){
+ return Math.max.apply(null, this);
+ },
+
+ average: function(){
+ return this.length ? this.sum() / this.length : 0;
+ },
+
+ sum: function(){
+ var result = 0, l = this.length;
+ if (l){
+ while (l--){
+ if (this[l] != null) result += parseFloat(this[l]);
+ }
+ }
+ return result;
+ },
+
+ unique: function(){
+ return [].combine(this);
+ },
+
+ shuffle: function(){
+ for (var i = this.length; i && --i;){
+ var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+ this[i] = this[r];
+ this[r] = temp;
+ }
+ return this;
+ },
+
+ reduce: function(fn, value){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ reduceRight: function(fn, value){
+ var i = this.length;
+ while (i--){
+ if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
+ }
+ return value;
+ },
+
+ pluck: function(prop){
+ return this.map(function(item){
+ return item[prop];
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Date.Extras.js
+
+name: Date.Extras
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+ timeDiffInWords: function(to){
+ return Date.distanceOfTimeInWords(this, to || new Date);
+ },
+
+ timeDiff: function(to, separator){
+ if (to == null) to = new Date;
+ var delta = ((to - this) / 1000).floor().abs();
+
+ var vals = [],
+ durations = [60, 60, 24, 365, 0],
+ names = ['s', 'm', 'h', 'd', 'y'],
+ value, duration;
+
+ for (var item = 0; item < durations.length; item++){
+ if (item && !delta) break;
+ value = delta;
+ if ((duration = durations[item])){
+ value = (delta % duration);
+ delta = (delta / duration).floor();
+ }
+ vals.unshift(value + (names[item] || ''));
+ }
+
+ return vals.join(separator || ':');
+ }
+
+}).extend({
+
+ distanceOfTimeInWords: function(from, to){
+ return Date.getTimePhrase(((to - from) / 1000).toInt());
+ },
+
+ getTimePhrase: function(delta){
+ var suffix = (delta < 0) ? 'Until' : 'Ago';
+ if (delta < 0) delta *= -1;
+
+ var units = {
+ minute: 60,
+ hour: 60,
+ day: 24,
+ week: 7,
+ month: 52 / 12,
+ year: 12,
+ eon: Infinity
+ };
+
+ var msg = 'lessThanMinute';
+
+ for (var unit in units){
+ var interval = units[unit];
+ if (delta < 1.5 * interval){
+ if (delta > 0.75 * interval) msg = unit;
+ break;
+ }
+ delta /= interval;
+ msg = unit + 's';
+ }
+
+ delta = delta.round();
+ return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
+ }
+
+}).defineParsers(
+
+ {
+ // "today", "tomorrow", "yesterday"
+ re: /^(?:tod|tom|yes)/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ switch (bits[0]){
+ case 'tom': return d.increment();
+ case 'yes': return d.decrement();
+ default: return d;
+ }
+ }
+ },
+
+ {
+ // "next Wednesday", "last Thursday"
+ re: /^(next|last) ([a-z]+)$/i,
+ handler: function(bits){
+ var d = new Date().clearTime();
+ var day = d.getDay();
+ var newDay = Date.parseDay(bits[2], true);
+ var addDays = newDay - day;
+ if (newDay <= day) addDays += 7;
+ if (bits[1] == 'last') addDays -= 7;
+ return d.set('date', d.getDate() + addDays);
+ }
+ }
+
+).alias('timeAgoInWords', 'timeDiffInWords');
+
+/*
+---
+
+name: Hash
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+ - Core/Object
+ - MooTools.More
+
+provides: [Hash]
+
+...
+*/
+
+(function(){
+
+if (this.Hash) return;
+
+var Hash = this.Hash = new Type('Hash', function(object){
+ if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+});
+
+this.$H = function(object){
+ return new Hash(object);
+};
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ Object.forEach(this, fn, bind);
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('each', 'forEach');
+
+Hash.implement({
+
+ has: Object.prototype.hasOwnProperty,
+
+ keyOf: function(value){
+ return Object.keyOf(this, value);
+ },
+
+ hasValue: function(value){
+ return Object.contains(this, value);
+ },
+
+ extend: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.set(this, key, value);
+ }, this);
+ return this;
+ },
+
+ combine: function(properties){
+ Hash.each(properties || {}, function(value, key){
+ Hash.include(this, key, value);
+ }, this);
+ return this;
+ },
+
+ erase: function(key){
+ if (this.hasOwnProperty(key)) delete this[key];
+ return this;
+ },
+
+ get: function(key){
+ return (this.hasOwnProperty(key)) ? this[key] : null;
+ },
+
+ set: function(key, value){
+ if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+ return this;
+ },
+
+ empty: function(){
+ Hash.each(this, function(value, key){
+ delete this[key];
+ }, this);
+ return this;
+ },
+
+ include: function(key, value){
+ if (this[key] == undefined) this[key] = value;
+ return this;
+ },
+
+ map: function(fn, bind){
+ return new Hash(Object.map(this, fn, bind));
+ },
+
+ filter: function(fn, bind){
+ return new Hash(Object.filter(this, fn, bind));
+ },
+
+ every: function(fn, bind){
+ return Object.every(this, fn, bind);
+ },
+
+ some: function(fn, bind){
+ return Object.some(this, fn, bind);
+ },
+
+ getKeys: function(){
+ return Object.keys(this);
+ },
+
+ getValues: function(){
+ return Object.values(this);
+ },
+
+ toQueryString: function(base){
+ return Object.toQueryString(this, base);
+ }
+
+});
+
+Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
+
+
+})();
+
+
+/*
+---
+
+script: Hash.Extras.js
+
+name: Hash.Extras
+
+description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Hash
+ - Object.Extras
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+ getFromPath: function(notation){
+ return Object.getFromPath(this, notation);
+ },
+
+ cleanValues: function(method){
+ return new Hash(Object.cleanValues(this, method));
+ },
+
+ run: function(){
+ Object.run(arguments);
+ }
+
+});
+
+/*
+---
+name: Number.Format
+description: Extends the Number Type object to include a number formatting method.
+license: MIT-style license
+authors: [Arian Stolwijk]
+requires: [Core/Number, Locale.en-US.Number]
+# Number.Extras is for compatibility
+provides: [Number.Format, Number.Extras]
+...
+*/
+
+
+Number.implement({
+
+ format: function(options){
+ // Thanks dojo and YUI for some inspiration
+ var value = this;
+ options = options ? Object.clone(options) : {};
+ var getOption = function(key){
+ if (options[key] != null) return options[key];
+ return Locale.get('Number.' + key);
+ };
+
+ var negative = value < 0,
+ decimal = getOption('decimal'),
+ precision = getOption('precision'),
+ group = getOption('group'),
+ decimals = getOption('decimals');
+
+ if (negative){
+ var negativeLocale = getOption('negative') || {};
+ if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
+ ['prefix', 'suffix'].each(function(key){
+ if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
+ });
+
+ value = -value;
+ }
+
+ var prefix = getOption('prefix'),
+ suffix = getOption('suffix');
+
+ if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
+ if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
+
+ value += '';
+ var index;
+ if (getOption('scientific') === false && value.indexOf('e') > -1){
+ var match = value.split('e'),
+ zeros = +match[1];
+ value = match[0].replace('.', '');
+
+ if (zeros < 0){
+ zeros = -zeros - 1;
+ index = match[0].indexOf('.');
+ if (index > -1) zeros -= index - 1;
+ while (zeros--) value = '0' + value;
+ value = '0.' + value;
+ } else {
+ index = match[0].lastIndexOf('.');
+ if (index > -1) zeros -= match[0].length - index - 1;
+ while (zeros--) value += '0';
+ }
+ }
+
+ if (decimal != '.') value = value.replace('.', decimal);
+
+ if (group){
+ index = value.lastIndexOf(decimal);
+ index = (index > -1) ? index : value.length;
+ var newOutput = value.substring(index),
+ i = index;
+
+ while (i--){
+ if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
+ newOutput = value.charAt(i) + newOutput;
+ }
+
+ value = newOutput;
+ }
+
+ if (prefix) value = prefix + value;
+ if (suffix) value += suffix;
+
+ return value;
+ },
+
+ formatCurrency: function(decimals){
+ var locale = Locale.get('Number.currency') || {};
+ if (locale.scientific == null) locale.scientific = false;
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ },
+
+ formatPercentage: function(decimals){
+ var locale = Locale.get('Number.percentage') || {};
+ if (locale.suffix == null) locale.suffix = '%';
+ locale.decimals = decimals != null ? decimals
+ : (locale.decimals == null ? 2 : locale.decimals);
+
+ return this.format(locale);
+ }
+
+});
+
+/*
+---
+
+script: URI.js
+
+name: URI
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - Core/Object
+ - Core/Class
+ - Core/Class.Extras
+ - Core/Element
+ - String.QueryString
+
+provides: [URI]
+
+...
+*/
+
+(function(){
+
+var toString = function(){
+ return this.get('value');
+};
+
+var URI = this.URI = new Class({
+
+ Implements: Options,
+
+ options: {
+ /*base: false*/
+ },
+
+ regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+ parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+ schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+ initialize: function(uri, options){
+ this.setOptions(options);
+ var base = this.options.base || URI.base;
+ if (!uri) uri = base;
+
+ if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
+ else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+ },
+
+ parse: function(value, base){
+ var bits = value.match(this.regex);
+ if (!bits) return false;
+ bits.shift();
+ return this.merge(bits.associate(this.parts), base);
+ },
+
+ merge: function(bits, base){
+ if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+ if (base){
+ this.parts.every(function(part){
+ if (bits[part]) return false;
+ bits[part] = base[part] || '';
+ return true;
+ });
+ }
+ bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+ bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+ return bits;
+ },
+
+ parseDirectory: function(directory, baseDirectory){
+ directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+ if (!directory.test(URI.regs.directoryDot)) return directory;
+ var result = [];
+ directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+ if (dir == '..' && result.length > 0) result.pop();
+ else if (dir != '.') result.push(dir);
+ });
+ return result.join('/') + '/';
+ },
+
+ combine: function(bits){
+ return bits.value || bits.scheme + '://' +
+ (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+ (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+ (bits.directory || '/') + (bits.file || '') +
+ (bits.query ? '?' + bits.query : '') +
+ (bits.fragment ? '#' + bits.fragment : '');
+ },
+
+ set: function(part, value, base){
+ if (part == 'value'){
+ var scheme = value.match(URI.regs.scheme);
+ if (scheme) scheme = scheme[1];
+ if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
+ else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+ } else if (part == 'data'){
+ this.setData(value);
+ } else {
+ this.parsed[part] = value;
+ }
+ return this;
+ },
+
+ get: function(part, base){
+ switch (part){
+ case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+ case 'data' : return this.getData();
+ }
+ return this.parsed[part] || '';
+ },
+
+ go: function(){
+ document.location.href = this.toString();
+ },
+
+ toURI: function(){
+ return this;
+ },
+
+ getData: function(key, part){
+ var qs = this.get(part || 'query');
+ if (!(qs || qs === 0)) return key ? null : {};
+ var obj = qs.parseQueryString();
+ return key ? obj[key] : obj;
+ },
+
+ setData: function(values, merge, part){
+ if (typeof values == 'string'){
+ var data = this.getData();
+ data[arguments[0]] = arguments[1];
+ values = data;
+ } else if (merge){
+ values = Object.merge(this.getData(null, part), values);
+ }
+ return this.set(part || 'query', Object.toQueryString(values));
+ },
+
+ clearData: function(part){
+ return this.set(part || 'query', '');
+ },
+
+ toString: toString,
+ valueOf: toString
+
+});
+
+URI.regs = {
+ endSlash: /\/$/,
+ scheme: /^(\w+):/,
+ directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
+
+String.implement({
+
+ toURI: function(options){
+ return new URI(this, options);
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: URI.Relative.js
+
+name: URI.Relative
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - Class.refactor
+ - URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+ combine: function(bits, base){
+ if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+ return this.previous.apply(this, arguments);
+ var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+ if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+ var baseDir = base.directory.split('/'),
+ relDir = bits.directory.split('/'),
+ path = '',
+ offset;
+
+ var i = 0;
+ for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+ for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+ for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+ return (path || (bits.file ? '' : './')) + end;
+ },
+
+ toAbsolute: function(base){
+ base = new URI(base);
+ if (base) base.set('directory', '').set('file', '');
+ return this.toRelative(base);
+ },
+
+ toRelative: function(base){
+ return this.get('value', new URI(base));
+ }
+
+});
+
+/*
+---
+
+script: Assets.js
+
+name: Assets
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Element.Event
+ - MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+ javascript: function(source, properties){
+ if (!properties) properties = {};
+
+ var script = new Element('script', {src: source, type: 'text/javascript'}),
+ doc = properties.document || document,
+ load = properties.onload || properties.onLoad;
+
+ delete properties.onload;
+ delete properties.onLoad;
+ delete properties.document;
+
+ if (load){
+ if (!script.addEventListener){
+ script.addEvent('readystatechange', function(){
+ if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
+ });
+ } else {
+ script.addEvent('load', load);
+ }
+ }
+
+ return script.set(properties).inject(doc.head);
+ },
+
+ css: function(source, properties){
+ if (!properties) properties = {};
+
+ var load = properties.onload || properties.onLoad,
+ doc = properties.document || document,
+ timeout = properties.timeout || 3000;
+
+ ['onload', 'onLoad', 'document'].each(function(prop){
+ delete properties[prop];
+ });
+
+ var link = new Element('link', {
+ type: 'text/css',
+ rel: 'stylesheet',
+ media: 'screen',
+ href: source
+ }).setProperties(properties).inject(doc.head);
+
+ if (load){
+ // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
+ var loaded = false, retries = 0;
+ var check = function(){
+ var stylesheets = document.styleSheets;
+ for (var i = 0; i < stylesheets.length; i++){
+ var file = stylesheets[i];
+ var owner = file.ownerNode ? file.ownerNode : file.owningElement;
+ if (owner && owner == link){
+ loaded = true;
+ return load.call(link);
+ }
+ }
+ retries++;
+ if (!loaded && retries < timeout / 50) return setTimeout(check, 50);
+ }
+ setTimeout(check, 0);
+ }
+ return link;
+ },
+
+ image: function(source, properties){
+ if (!properties) properties = {};
+
+ var image = new Image(),
+ element = document.id(image) || new Element('img');
+
+ ['load', 'abort', 'error'].each(function(name){
+ var type = 'on' + name,
+ cap = 'on' + name.capitalize(),
+ event = properties[type] || properties[cap] || function(){};
+
+ delete properties[cap];
+ delete properties[type];
+
+ image[type] = function(){
+ if (!image) return;
+ if (!element.parentNode){
+ element.width = image.width;
+ element.height = image.height;
+ }
+ image = image.onload = image.onabort = image.onerror = null;
+ event.delay(1, element, element);
+ element.fireEvent(name, element, 1);
+ };
+ });
+
+ image.src = element.src = source;
+ if (image && image.complete) image.onload.delay(1);
+ return element.set(properties);
+ },
+
+ images: function(sources, options){
+ sources = Array.from(sources);
+
+ var fn = function(){},
+ counter = 0;
+
+ options = Object.merge({
+ onComplete: fn,
+ onProgress: fn,
+ onError: fn,
+ properties: {}
+ }, options);
+
+ return new Elements(sources.map(function(source, index){
+ return Asset.image(source, Object.append(options.properties, {
+ onload: function(){
+ counter++;
+ options.onProgress.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ },
+ onerror: function(){
+ counter++;
+ options.onError.call(this, counter, index, source);
+ if (counter == sources.length) options.onComplete();
+ }
+ }));
+ }));
+ }
+
+};
+
+/*
+---
+
+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;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Group.js
+
+name: Group
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Events
+ - MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+(function(){
+
+this.Group = new Class({
+
+ initialize: function(){
+ this.instances = Array.flatten(arguments);
+ },
+
+ addEvent: function(type, fn){
+ var instances = this.instances,
+ len = instances.length,
+ togo = len,
+ args = new Array(len),
+ self = this;
+
+ instances.each(function(instance, i){
+ instance.addEvent(type, function(){
+ if (!args[i]) togo--;
+ args[i] = arguments;
+ if (!togo){
+ fn.call(self, instances, instance, args);
+ togo = len;
+ args = new Array(len);
+ }
+ });
+ });
+ }
+
+});
+
+})();
+
+/*
+---
+
+script: Hash.Cookie.js
+
+name: Hash.Cookie
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - Core/Cookie
+ - Core/JSON
+ - MooTools.More
+ - Hash
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+ Extends: Cookie,
+
+ options: {
+ autoSave: true
+ },
+
+ initialize: function(name, options){
+ this.parent(name, options);
+ this.load();
+ },
+
+ save: function(){
+ var value = JSON.encode(this.hash);
+ if (!value || value.length > 4096) return false; //cookie would be truncated!
+ if (value == '{}') this.dispose();
+ else this.write(value);
+ return true;
+ },
+
+ load: function(){
+ this.hash = new Hash(JSON.decode(this.read(), true));
+ return this;
+ }
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+ if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+ var value = method.apply(this.hash, arguments);
+ if (this.options.autoSave) this.save();
+ return value;
+ });
+});
+
+/*
+---
+
+name: Swiff
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits:
+ - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires: [Core/Options, Core/Object, Core/Element]
+
+provides: Swiff
+
+...
+*/
+
+(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 ('ActiveXObject' in window){
+ 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);
+};
+
+})();
+
+/*
+---
+name: Table
+description: LUA-Style table implementation.
+license: MIT-style license
+authors:
+ - Valerio Proietti
+requires: [Core/Array]
+provides: [Table]
+...
+*/
+
+(function(){
+
+var Table = this.Table = function(){
+
+ this.length = 0;
+ var keys = [],
+ values = [];
+
+ this.set = function(key, value){
+ var index = keys.indexOf(key);
+ if (index == -1){
+ var length = keys.length;
+ keys[length] = key;
+ values[length] = value;
+ this.length++;
+ } else {
+ values[index] = value;
+ }
+ return this;
+ };
+
+ this.get = function(key){
+ var index = keys.indexOf(key);
+ return (index == -1) ? null : values[index];
+ };
+
+ this.erase = function(key){
+ var index = keys.indexOf(key);
+ if (index != -1){
+ this.length--;
+ keys.splice(index, 1);
+ return values.splice(index, 1)[0];
+ }
+ return null;
+ };
+
+ this.each = this.forEach = function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
+ };
+
+};
+
+if (this.Type) new Type('Table', Table);
+
+})();
diff --git a/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/Purr/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/Next/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/Next/lib/MooTools/TinyTab/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/Next/tml/admin.html b/pyload/webui/themes/Next/tml/admin.html
new file mode 100644
index 000000000..5c71cbac5
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/admin.html
@@ -0,0 +1,100 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/js/admin.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+ <div class="btn-group">
+ <a href="#" id="quit-pyload" class="btn btn-default"><span class="glyphicon glyphicon-off"></span> {{_("Quit pyLoad")}}</a>
+ <a href="#" id="restart-pyload" class="btn btn-default"><span class="glyphicon glyphicon-repeat"></span> {{_("Restart pyLoad")}}</a>
+</div>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyLoadCore.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+<br>
+<br>
+ <form action="" method="POST" >
+ <table class="settable table" style="width:50%;">
+ <thead>
+ <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 btn btn-default btn-xs" href="#" id="change_pw|{{name}}"><span class="glyphicon glyphicon-pencil"></span> {{ _("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="btn btn-primary" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" style="z-index: 2">
+ <form id="password_form" class="from-group" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h3>{{ _("Change Password") }}</h3>
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+
+<div class="form-group">
+ <label for="user_login">{{ _("User") }}</label>
+ <input class="form-control" id="user_login" name="user_login" type="text"/>
+ <p class="help-block">{{ _("Your username.") }}</p>
+ </div>
+ <div class="form-group">
+ <label for="login_current_password">{{ _("Current password") }}</label>
+ <input class="form-control" id="login_current_password" name="login_current_password" type="password"/>
+ <p class="help-block">{{ _("The password for this account.") }}</p>
+ </div>
+ <div class="form-group">
+ <label for="login_new_password">{{ _("New password") }}</label>
+ <input class="form-control" id="login_new_password" name="login_new_password" type="password"/>
+ <p class="help-block">{{ _("The new password.") }}</p>
+ </div>
+ <div class="form-group">
+ <label for="login_new_password2">{{ _("New password (repeat)") }}</label>
+ <input class="form-control" id="login_new_password2" name="login_new_password2" type="password" />
+ <p class="help-block">{{ _("Please repeat the new password.") }}</p>
+ </div>
+
+
+
+ <button class="btn btn-primary" id="login_password_button" type="submit" style="float: right">{{ _("Submit") }}</button>
+ <button class="btn btn-default" id="login_password_reset" style="margin-right: 5px; float: right" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/base.html b/pyload/webui/themes/Next/tml/base.html
new file mode 100644
index 000000000..81c1f3008
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/base.html
@@ -0,0 +1,199 @@
+<?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="/css/window.css"/>
+<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/>
+<link rel="stylesheet" href="/lib/Bootstrap/css/bootstrap.css">
+
+<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script>
+<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script>
+<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script>
+
+
+<script type="text/javascript" src="/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 %}
+
+
+
+
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <nav class="navbar navbar-default">
+ <div class="container-fluid">
+ <!-- Brand and toggle get grouped for better mobile display -->
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" style="height:30px;"/></a>
+ </div>
+
+ <a href="/"></a>
+
+ <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+ <ul class="nav navbar-nav">
+
+ {% 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=""><span class="glyphicon glyphicon-home"></span> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><span class="glyphicon glyphicon-tasks"></span> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><span class="glyphicon glyphicon-magnet"></span> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""> <span class="glyphicon glyphicon-download"></span> {{_("Downloads")}}</a>
+ </li>
+{# <li {{ selected('filemanager') }}>#}
+{# <a href="/filemanager/" title=""><span class="glyphicon glyphicon-magnet"></span> {{_("FileManager")}}</a>#}
+{# </li>#}
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><span class="glyphicon glyphicon-list"></span> {{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><span class="glyphicon glyphicon-wrench"></span> {{_("Config")}}</a>
+ </li>
+ {% endblock %}
+
+
+ </ul>
+ <ul class="nav navbar-nav navbar-right">
+ <li><a href="/info" class="action info" rel="nofollow"><span class="glyphicon glyphicon-user"></span> {{user.name}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow"><span class="glyphicon glyphicon-cog"></span></a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow"><span class="glyphicon glyphicon-info-sign"></span></a></li>
+
+ </ul>
+ </div><!-- /.navbar-collapse -->
+ </div><!-- /.container-fluid -->
+
+ </div>
+</nav>
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-left:10px;">
+ <button id="action_play" class="btn btn-default" href="#"><span class="glyphicon glyphicon-play"></span>&nbsp;</button>
+ <button id="action_stop" type="button" class="btn btn-default"><span class="glyphicon glyphicon-stop"></span>&nbsp;</button>
+ <button id="action_cancel" type="button" class="btn btn-default"><span class="glyphicon glyphicon-remove"></span>&nbsp;</button>
+ <button id="action_add" type="button" class="btn btn-default"><span class="glyphicon glyphicon-plus"></span>&nbsp;</button>
+</div>
+
+
+{% endif %}
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<button id="action_add" type="button" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-barcode"></span><span> {{_("Captcha waiting")}}</span></button>
+</span>
+
+
+{% if perms.LIST %}
+
+<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-right:10px; float:right;">
+ <button id="action_play" class="btn btn-default"><span >{{_("Download:")}}</span>&nbsp;<span class="label label-{% if status.download %}success{% else %}danger{% endif %}">{% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</span></button>
+ <button id="action_stop" type="button" class="btn btn-default"><span>{{_("Reconnect:")}}</span>&nbsp;<span class="label label-{% if status.reconnect %}success{% else %}danger{% endif %}">{% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</span></button>
+ <button id="action_cancel" type="button" class="btn btn-default"><span class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></span></button>
+ <button id="action_add" type="button" class="btn btn-default"><span 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></span></button>
+</div>
+
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" style="width: 98%; margin-left:10px; margin-rigth:10px;" lang="en" dir="ltr">
+
+<h3>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h3>
+
+{% block statusbar %}
+{% endblock %}
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot" style="with: 98%; margin-left: 10px; margin-right:10px">&copy; 2008-2015 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 "/tml/window.html" %}
+ {% include "/tml/captcha.html" %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
+</body>
+</html>
diff --git a/pyload/webui/themes/Next/tml/captcha.html b/pyload/webui/themes/Next/tml/captcha.html
new file mode 100644
index 000000000..2b449aa86
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/captcha.html
@@ -0,0 +1,40 @@
+<!-- Captcha box -->
+<div id="cap_box" >
+
+ <form id="cap_form" class="form-group" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h3>{{_("Captcha reading")}}</h3>
+ <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="" />
+
+ <div class="form-group">
+ <label>{{_("Captcha")}}</label>
+ <span ></br>
+ <img id="cap_textual_img" style="border: 1px solid #bbb; padding: 3px 3px 3px 3px;" src="">
+ </span>
+ </div>
+ <div class="form-group">
+ <label>{{_("Text")}}</label>
+ <input class="form-control" id="cap_result" name="cap_result" type="text" size="20" />
+ <p class="small">{{_("Input the text on the captcha.")}}</p>
+ </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 class="btn btn-primary" id="cap_submit" type="submit" style="float: right; margin-left: 5px;">{{_("Submit")}}</button>
+ <button class="btn btn-default" id="cap_reset" type="reset" style="float: right">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
diff --git a/pyload/webui/themes/Next/tml/downloads.html b/pyload/webui/themes/Next/tml/downloads.html
new file mode 100644
index 000000000..0643ed2f8
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul style="list-style-type: none;">
+ {% for folder in files.folder %}
+ <li style="list-style-type: none;">
+ <span style="margin-right: 5px" class="glyphicon glyphicon-folder-close"></span>{{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li style="list-style-type: none;"><span style="margin-right: 5px" class="glyphicon glyphicon-file"></span><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li style="list-style-type: none;"> <span style="margin-right: 5px" class="glyphicon glyphicon-file"></span><a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/filemanager.html b/pyload/webui/themes/Next/tml/filemanager.html
new file mode 100644
index 000000000..c940f3f79
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/filemanager.html
@@ -0,0 +1,80 @@
+{% extends '/tml/base.html' %}
+
+{% block head %}
+
+<script type="text/javascript" src="/js/filemanager.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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/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 "/tml/rename_directory.html" %}
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/folder.html b/pyload/webui/themes/Next/tml/folder.html
new file mode 100644
index 000000000..d01fc4972
--- /dev/null
+++ b/pyload/webui/themes/Next/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="/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/Next/tml/home.html b/pyload/webui/themes/Next/tml/home.html
new file mode 100644
index 000000000..2ff1ad58b
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/home.html
@@ -0,0 +1,277 @@
+{% extends '/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,
+ }
+ }),
+ status: new Element('td', {
+ 'html': '&nbsp;',
+ }),
+ statusspan: new Element('span', {
+ 'html': item.statusmsg,
+ 'class': 'label label-default',
+ 'styles':{
+
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ 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('span',{
+ 'html': '',
+ 'class': 'glyphicon glyphicon-remove',
+ 'styles':{
+ 'margin-left': '3px',
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':'',
+ 'styles':{
+ 'border-top-color': '#fff',
+ }
+ }),
+ progress: new Element('div', {
+ 'html':'',
+ 'class':'progress',
+ 'styles':{
+ 'margin-bottom': '0px',
+ }
+ }),
+ pgb: new Element('div', {
+ 'html':'',
+ 'class':'progress-bar progress-bar-striped active',
+ 'role':'progress',
+ 'styles':{
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+
+ };
+
+
+ this.elements.status.adopt(this.elements.statusspan);
+ this.elements.progress.adopt(this.elements.pgb);
+ this.elements.tr.adopt(this.elements.status,this.elements.name,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.progress));
+ 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.statusspan.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 (item.statusmsg == "waiting") {
+ this.elements.statusspan.set('class', 'label label-warning')
+ } else if (item.statusmsg == "starting") {
+ this.elements.statusspan.set('class', 'label label-info')
+ } else if (item.statusmsg == "downloading") {
+ this.elements.statusspan.set('class', 'label label-success')
+ } else if (item.stausmsg == "extracting") {
+ this.elements.statusspan.set('class', 'label label-primary')
+ } else {
+ this.elements.statusspan.set('class', 'label label-default')
+ }
+ if(!operafix)
+ {
+
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),80,70].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(50/200*item.percent),0,200].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 content %}
+<table class="table" style="width:100%;">
+ <thead>
+ <tr class="header">
+ <th>{{_("Status")}}</th>
+ <th>{{_("Name")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+</br>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_status"><span class="label label-{% if link.status == 'downloading' %}success{% endif %}{% if link.status == 'extracting' %}primary{% endif %}{% if link.status == 'starting' %}warning{% else %}default{% endif %}">{{ link.status }}</span></td>
+ <td id="link_{{ link.id }}_name">{{ link.name }}</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="/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>
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/info.html b/pyload/webui/themes/Next/tml/info.html
new file mode 100644
index 000000000..f6c3a91e1
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/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.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><b>{{ _("Python:") }}</b></td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("OS:") }}</b></td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("pyLoad version:") }}</b></td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Installation Folder:") }}</b></td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Config Folder:") }}</b></td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Download Folder:") }}</b></td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Free Space:") }}</b></td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Language:") }}</b></td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Webinterface Port:") }}</b></td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td><b>{{ _("Remote Interface Port:") }}</b></td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/login.html b/pyload/webui/themes/Next/tml/login.html
new file mode 100644
index 000000000..93d50d64d
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/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 pyLoadCore.py -u</b>
+{% endif %}
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/logout.html b/pyload/webui/themes/Next/tml/logout.html
new file mode 100644
index 000000000..db7e9290e
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/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/Next/tml/logs.html b/pyload/webui/themes/Next/tml/logs.html
new file mode 100644
index 000000000..f0f25a9dd
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/css/log.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}"><span class="glyphicon glyphicon-fast-backward"></span></a> <a href="{{ "/logs/" + iprev|string }}"><span class="glyphicon glyphicon-step-backward"></span></a> <a href="{{ "/logs/" + inext|string }}"><span class="glyphicon glyphicon-step-forward"></span></a> <a href="/logs/"><span class="glyphicon glyphicon-fast-forward"></span></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 class="{{color_template}}{{line.level}}">{{line.date}}</td><td class="loglevel loglevel{{color_template}}{{line.level}}"><span>{{line.level}}</span></td><td class="{{color_template}}{{line.level}}">{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input style="float:left; width:80%;" class="form-control" type="text" name="from" size="15" value="{{from}}"/>
+ <input style="float:left; width:19%; margin-left: 1%;" class="btn btn-primary" type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/pathchooser.html b/pyload/webui/themes/Next/tml/pathchooser.html
new file mode 100644
index 000000000..9f61d282d
--- /dev/null
+++ b/pyload/webui/themes/Next/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="/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>
diff --git a/pyload/webui/themes/Next/tml/queue.html b/pyload/webui/themes/Next/tml/queue.html
new file mode 100644
index 000000000..0e343a754
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/queue.html
@@ -0,0 +1,109 @@
+{% extends '/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/js/package.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 pageactions %}
+<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-left:10px;">
+ <button id="del_finished" class="btn btn-default"><span>{{_("Delete Finished")}}</span></button>
+ <button id="restart_failed" class="btn btn-default"><span>{{_("Restart Failed")}}</span></button>
+</div>
+{% endblock %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% 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}}" >
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="float:left; width: 50%; cursor: pointer">
+ <span class="glyphicon glyphicon-folder-close"></span>
+ <span class="name" style="font-size: 16px; font-weight: bold;"><em class="package_drag" style="font-style:normal">{{package.name }}</em></span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <span class="glyphicon glyphicon-trash" title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" /></span>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-repeat" title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" /></span>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-pencil" title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" /></span>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-transfer" title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" /></span>
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" class="progress" style="float:left; width: 50%; margin-top: -5px; color:#fff; font-weight: 700; font-size:12px;">
+ <div class="progress-bar" role="progressbar" style="width: {{ progress }}%; height: 100%; position: relative; z-index: 1;">
+ <label>{{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <label style="right: 30px; position: absolute; z-index: 2; color:#fff;">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+
+ <div id="children_{{package.pid}}" style="display: none; margin-bottom: 15px;" class="children">
+ <span class="child_secrow" style="margin-bottom: 30px; margin-top: 5px;">{{_("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" style="z-index: 2">
+ <form id="pack_form" class="from-group" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h3>{{_("Edit Package")}}</h3>
+ <p>{{_("Edit the package detais below.")}}</p>
+
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+
+ <div class="form-group">
+ <label for="pack_name">{{_("Name")}}</label>
+ <input class="form-control" id="pack_name" name="pack_name" type="text" />
+ <p class="help-block">{{_("The name of the package.")}}</p>
+ </div>
+ <div class="form-group">
+ <label for="pack_folder">{{_("Folder")}}</label>
+ <input class="form-control" id="pack_folder" name="pack_folder" type="text" />
+ <p class="help-block">{{_("Name of subfolder for these downloads.")}}</p>
+ </div>
+ <div class="form-group">
+ <label for="pack_pws">{{_("Password")}}</label>
+ <textarea class="form-control" style=" width: 100%;" rows="3" name="pack_pws" id="pack_pws"></textarea>
+ <p class="help-block">{{_("List of passwords used for unrar.")}}</p>
+ </div>
+ <button class="btn btn-primary" style="float: right; margin-left: 5px;" type="submit">{{_("Submit")}}</button>
+ <button class="btn btn-default" id="pack_reset" style="float: right;" type="reset" >{{_("Reset")}}</button>
+
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/settings.html b/pyload/webui/themes/Next/tml/settings.html
new file mode 100644
index 000000000..5acab4f1f
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/settings.html
@@ -0,0 +1,212 @@
+{% extends '/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script>
+ <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script>
+ <script type="text/javascript" src="/js/settings.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="nav nav-tabs">
+ <li role="presentation" class"active"><a href="#">{{ _("General") }}</a></li>
+ <li role="presentation"><a href="#">{{ _("Plugins") }}</a></li>
+ <li role="presentation"><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" style="width: 20%; float:left;">
+ <li class>
+ <div class="panel panel-default" >
+ <div class="panel-body">
+ <ul id="general-menu" style="float: left; height: 600px; overflow: auto; overflow-x: hidden; width: 100%">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li style="list-style-type: none; cursor: pointer; margin-top: 10px;" id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+ </li>
+ </ul>
+
+ <form style="float: left; width:40%; margin-left: 20%; diplay:block; position: fixed; overflow: auto;" 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 class="btn btn-primary" style="float:right; margin-right: 10px;" id="general|submit" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs" style="width: 20%; float:left; hight:300px;">
+ <li class>
+ <div class="panel panel-default">
+ <div class="panel-body">
+ <ul id="plugin-menu" style="float: left; height: 600px; overflow: auto; overflow-x: hidden; width: 100%">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li style="list-style-type: none; cursor: pointer; margin-top: 10px;" id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ {% endfor %}
+ </ul>
+ <div>
+ </div>
+ </li>
+ </ul>
+
+
+ <form style="float: left; width:40%; margin-left: 10px;" id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content" style:"position: static;">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input class="btn btn-primary" style="float:right; margin-right: 10px;" 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 table">
+
+ <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_add" style="margin-left: 5px;" type="submit" class="btn btn-default">&nbsp;<span class="glyphicon glyphicon-plus"></button>
+
+ <button id="account_submit" type="submit" class="btn btn-primary" >{{_("Submit")}}</button>
+
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h3>{{_("Add Account")}}</h3>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+
+<div class="form-group">
+<label for="account_login">{{_("Login")}}</label>
+<input class="form-control" id="account_login" name="account_login" type="text" />
+<p >{{_("Your username.")}}</p>
+</div>
+<div class="form-group">
+<label for="account_password">{{_("Password")}}</label>
+<input class="form-control" id="account_password" name="account_password" type="password" size="20" />
+<p >{{_("The password for this account.")}}</p>
+</div>
+<div class="form-group">
+<label for="account_type">{{_("Type")}}</label>
+<p>{{_("Choose the hoster for your account.")}}</p>
+</div>
+<div class="form-group">
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+</div>
+<button class="btn btn-primary" style="float: right; margin-left: 5px;" id="account_add_button" type="submit">{{_("Add")}}</button>
+<button class="btn btn-default" style="float: right;" id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/Next/tml/settings_item.html b/pyload/webui/themes/Next/tml/settings_item.html
new file mode 100644
index 000000000..72566950f
--- /dev/null
+++ b/pyload/webui/themes/Next/tml/settings_item.html
@@ -0,0 +1,50 @@
+<table class="settable table">
+
+ {% if section.outline %}
+ <tr><th colspan="2">{{ section.outline }}</th></tr>
+ {% endif %}
+ {% for okey, option in sorted_conf(section) %}
+ {% 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 style="float: right; margin-bottom: 5px;" class="form-control" name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input style="float: right;" class="form-control btn btn-primary" 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 style="float: right; margin-bottom: 5px;" class="form-control" name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input style="float: right;" class="form-control btn btn-primary" 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 style="float: right;" class="form-control" id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="password" value="{{option.value}}"/>
+ {% else %}
+ <input style="float: right;" class="form-control" id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="text" value="{{option.value}}"/>
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+
+</table>
diff --git a/pyload/webui/themes/Next/tml/window.html b/pyload/webui/themes/Next/tml/window.html
new file mode 100644
index 000000000..87c78f96c
--- /dev/null
+++ b/pyload/webui/themes/Next/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="from-group">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h3>{{_("Add Package")}}</h3>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<div class="form-group">
+ <label for="add_name">{{_("Name")}}</label>
+ <input id="add_name" class="form-control" name="add_name" type="text" />
+ <p class="help-block">{{_("The name of the new package.")}}</p>
+ </div>
+ <div class="form-group">
+ <label for="add_links">{{_("Links")}}</label>
+ </div>
+ <div>
+ <textarea class="form-control" rows="5" style="width: 100%" name="add_links" id="add_links"></textarea>
+</div>
+<div class="form-group">
+ <p class="help-block">{{_("The name of the new package.")}} {{ _("Filter urls") }} <span class=" glyphicon glyphicon-filter" onclick="parseUri()"></span></p>
+ </div>
+ <div class="form-group">
+ <label for="add_password">{{_("Password")}}</label>
+ <input id="add_password" class="form-control" name="add_password" type="text">
+ <p class="help-block">{{_("Password for RAR-Archive")}}</p>
+ </div>
+ <div class="form-group">
+ <label>{{_("File")}}</label>
+ <input type="file" name="add_file" id="add_file"/>
+ <p class="help-block">{{_("Upload a container.")}}</p>
+ </div>
+ <div class="form-group">
+ <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>
+ </div>
+ <button type="submit" class="btn btn-primary" style="float: right; margin-right: 5px;">{{_("Add Package")}}</button>
+ <button id="add_reset" class="btn btn-default" style="float: right; margin-right: 5px;" type="reset">{{_("Reset")}}</button>
+</form>
+
+
+
+</div>
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/setup.py b/setup.py
new file mode 100755
index 000000000..30d91c6ae
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import setuptools
+import sys
+
+import pyload
+
+
+PROJECT_DIR = path.abspath(path.join(__file__, ".."))
+
+setuptools.setup(
+ name="pyload",
+
+ version=pyload.__version__,
+
+ description=pyload.__description__,
+
+ long_description=open(path.join(PROJECT_DIR, "README.md")).read(),
+
+ keywords=["pyload", "download", "download-manager", "downloader", "jdownloader", "pycurl", "one-click-hoster"],
+
+ url=pyload.__website__,
+
+ download_url="https://github.com/pyload/pyload/releases",
+
+ license=pyload.__license__,
+
+ author="pyLoad Team",
+
+ author_email="support@pyload.org",
+
+ platforms=['Any'],
+
+ # package_dir={'pyload': 'src'},
+
+ packages=['pyload'],
+
+ # package_data=find_package_data(),
+
+ # data_files=[],
+
+ include_package_data=True,
+
+ exclude_package_data={'pyload': ["docs*", "locale*", "tests*"]}, #: exluced from build but not from sdist
+
+ install_requires=[
+ "Beaker >= 1.6",
+ "bitmath",
+ "bottle >= 0.10.0",
+ "colorama",
+ "Getch",
+ "jinja2",
+ "markupsafe",
+ "MultipartPostHandler",
+ "pycurl",
+ "rename_process",
+ "SafeEval",
+ "thrift >= 0.8.0",
+ "wsgiserver"
+ ],
+
+ extras_require={
+ 'Few plugins dependencies': ["BeautifulSoup >= 3.2, < 3.3"],
+ 'Captcha recognition' : ["PIL"],
+ 'Trash support' : ["Send2Trash"],
+ 'Colored log' : ["colorlog"],
+ 'Lightweight webserver' : ["bjoern"],
+ 'SSL support' : ["pyOpenSSL"],
+ 'RSDF/CCF/DLC support' : ["pycrypto"],
+ 'JSON speedup' : ["simplejson"]
+ },
+
+ # setup_requires=["setuptools_hg"],
+
+ # test_suite='nose.collector',
+
+ # tests_require=['nose', 'websocket-client >= 0.8.0', 'requests >= 1.2.2'],
+
+ entry_points={'console_scripts': ['pyload = pyload.Core:main']},
+
+ zip_safe=False,
+
+ classifiers=[
+ "Development Status :: %(code)s - %(status)s" % {'code' : pyload.__status_code__,
+ 'status': pyload.__status__},
+ "Environment :: Console",
+ "Environment :: Web Environment",
+ "Intended Audience :: End Users/Desktop",
+ "License :: OSI Approved :: %s" % pyload.__license__,
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.5",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Topic :: Internet :: WWW/HTTP"
+ ]
+)
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..d2a069c78
--- /dev/null
+++ b/tests/APIExerciser.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import gc
+import math
+import random
+import string
+import threading
+import time
+import traceback
+
+from pyload.remote.thriftbackend.ThriftClient import ThriftClient, Destination
+
+
+def createURLs():
+ """ create some urls, some may fail """
+ urls = []
+ for x in xrange(0, random.randint(20, 100)):
+ name = "DEBUG_API"
+ if random.randint(0, 5) == 5:
+ name = "" #: this link will fail
+
+ urls.append(name + "".join(random.sample(string.ascii_letters, random.randint(10, 20))))
+
+ return urls
+
+
+AVOID = (0, 3, 8)
+
+idPool = 0
+sumCalled = 0
+
+
+def startApiExerciser(core, n):
+ for _i in xrange(n):
+ APIExerciser(core).start()
+
+
+class APIExerciser(threading.Thread):
+
+ def __init__(self, core, thrift=False, user=None, pw=None):
+ global idPool
+
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+ self.core = core
+ self.count = 0 #: number of methods
+ self.time = time.time()
+
+ self.api = ThriftClient(user=user, password=pw) if thrift else core.api
+
+ self.id = idPool
+
+ idPool += 1
+
+ # self.start()
+
+
+ def run(self):
+
+ self.core.log.info("API Excerciser started %d" % self.id)
+
+ with open("error.log", "ab") as out:
+ # 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)
+ traceback.print_exc()
+ out.write(traceback.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.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())
+ # time.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 = random.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(random.sample(string.ascii_letters, 10))
+ urls = createURLs()
+
+ self.api.addPackage(name, urls, random.choice([Destination.Queue.Queue, Destination.Collector]))
+
+
+ def deleteFiles(self):
+ info = self.api.getQueueData()
+ if not info:
+ return
+
+ pack = random.choice(info)
+ fids = pack.links
+
+ if len(fids):
+ fids = [f.fid for f in random.sample(fids, random.randint(1, max(len(fids) / 2, 1)))]
+ self.api.deleteFiles(fids)
+
+
+ def deletePackages(self):
+ info = random.choice([self.api.getQueue(), self.api.getCollector()])
+ if not info:
+ return
+
+ pids = [p.pid for p in info]
+ if pids:
+ pids = random.sample(pids, random.randint(1, max(math.floor(len(pids) / 2.5), 1)))
+ self.api.deletePackages(pids)
+
+
+ def getFileData(self):
+ info = self.api.getQueueData()
+ if info:
+ p = random.choice(info)
+ if p.links:
+ self.api.getFileData(random.choice(p.links).fid)
+
+
+ def getPackageData(self):
+ info = self.api.getQueue()
+ if info:
+ self.api.getPackageData(random.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..e7fd17eb6
--- /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="remote" ${PYLOAD}
diff --git a/tests/code_analysis.sh b/tests/code_analysis.sh
new file mode 100644
index 000000000..aaf6bb6a4
--- /dev/null
+++ b/tests/code_analysis.sh
@@ -0,0 +1,15 @@
+#!/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"
diff --git a/tests/test_api.py b/tests/test_api.py
index f8901f731..ca02f7a2c 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,20 +1,24 @@
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from module.common import APIExerciser
-from nose.tools import nottest
+import nose
+import APIExerciser
-class TestApi:
+
+class TestApi(object):
def __init__(self):
self.api = APIExerciser.APIExerciser(None, True, "TestUser", "pwhere")
+
def test_login(self):
assert self.api.api.login("crapp", "wrong pw") is False
- #takes really long, only test when needed
- @nottest
- def test_random(self):
+ # takes really long, only test when needed
- for i in range(0, 100):
+
+ @nose.tools.nottest
+ def test_random(self):
+ for _i in xrange(0, 100):
self.api.testAPI()
diff --git a/tests/test_json.py b/tests/test_json.py
index ff56e8f5a..de2914182 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -1,48 +1,55 @@
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from urllib import urlencode
-from urllib2 import urlopen, HTTPError
-from json import loads
+import json
+import logging
+import urllib
+import urllib2
-from logging import log
url = "http://localhost:8001/api/%s"
-class TestJson:
+
+class TestJson(object):
def call(self, name, post=None):
if not post: post = {}
- post["session"] = self.key
- u = urlopen(url % name, data=urlencode(post))
- return loads(u.read())
+ post['session'] = self.key
+ u = urllib2.urlopen(url % name, data=urlencode(post))
+ return json.loads(u.read())
+
def setUp(self):
- u = urlopen(url % "login", data=urlencode({"username": "TestUser", "password": "pwhere"}))
- self.key = loads(u.read())
+ u = urllib2.urlopen(url % "login", data=urlencode({"username": "TestUser", "password": "pwhere"}))
+ self.key = json.loads(u.read())
assert self.key is not False
+
def test_wronglogin(self):
- u = urlopen(url % "login", data=urlencode({"username": "crap", "password": "wrongpw"}))
- assert loads(u.read()) is False
+ u = urllib2.urlopen(url % "login", data=urlencode({"username": "crap", "password": "wrongpw"}))
+ assert json.loads(u.read()) is False
+
def test_access(self):
try:
- urlopen(url % "getServerVersion")
- except HTTPError, e:
+ urllib2.urlopen(url % "getServerVersion")
+ except urllib2.HTTPError, e:
assert e.code == 403
else:
assert False
+
def test_status(self):
ret = self.call("statusServer")
- log(1, str(ret))
+ logging.log(1, str(ret))
assert "pause" in ret
assert "queue" in ret
+
def test_unknown_method(self):
try:
self.call("notExisting")
- except HTTPError, e:
+ except urllib2.HTTPError, e:
assert e.code == 404
else:
- assert False \ No newline at end of file
+ assert False